parameter pack constructor + const copy constructor not playing nicely

closed account (3hM2Nwbp)
I don't know how to technically explain it...but the snip is below. Why can't rhs in the copy constructor be const?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <array>
#include <cstdlib>
#include <iostream>

template<typename T, std::size_t N>
struct X
{
	std::array<T, N> elements;
	
	X() noexcept : elements {}
	{
		std::cout << "Default Constructor" << std::endl;
	}
	
	template<typename ...E>
	explicit X(E&&... elements) noexcept
		: elements {{ std::forward<E>(elements)... }}
	{
		std::cout << "Parameter pack constructor" << std::endl;
	}
		
	// Why can't rhs be a const reference?
	X(/*const*/ X& rhs)
	{
		std::cout << "Copy constructor" << std::endl;
	}
};

int main()
{
	typedef X<int, 3> X3I;
	X3I x0; // Default, zero-initialized (good)
	X3I x1(1, 2, 3); // parameter pack initialization (good)
	X3I x2(x0); // boom (uncomment /*const*/ in copy constructor)
}


main.cpp:17:17: error: no viable conversion from 'X<int, 3>' to 'int'
                : elements {{ std::forward<E>(elements)... }}
                              ^~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:33:6: note: in instantiation of function template specialization 'X<int, 3>::X<X<int, 3> &>' requested here
        X3I x2(x0); // boom
            ^
1 error generated.
Last edited on
Here's the error VS2013 gives
error C2664: 'std::array<T,3>::array(const std::array<T,3> &)' : cannot convert argument 1 from 'initializer-list' to 'const std::array<T,3> &'


Same error when I switch the array with a vector.
closed account (3hM2Nwbp)
Does it work with a non-const copy constructor?
closed account (oGbjE3v7)
The problem is, "x0" isn't const so it is trying to use the templated constructor instead of the copy constructor because of it. You can fix this by doing something like this to make them different:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <array>
#include <cstdlib>
#include <iostream>

template<typename T, std::size_t N>
struct X
{
	static_assert(N > 0, "mssg");

	std::array<T, N> elements;
	
	X() noexcept : elements {}
	{
		std::cout << "Default Constructor" << std::endl;
	}
	

	template<typename ...E>
	explicit X(T v, E&&... elements) noexcept
		: elements {{ v, std::forward<E>(elements)... }}
	{
		static_assert(sizeof...(E) == (N - 1), "more informative msg");
		std::cout << "Parameter pack constructor" << std::endl;
	}
		
	X(const X& rhs)
	{
		std::cout << "Copy constructor" << std::endl;
	}
};

int main()
{
	typedef X<int, 3> X3I;
	X3I x0; // Default, zero-initialized (good)
	X3I x1(1, 2, 3); // parameter pack initialization (good)
	X3I x2(x0); // boom (uncomment /*const*/ in copy constructor)
}


Or using a std::initializer_list<> instead.. which would make more sense: http://www.cplusplus.com/reference/initializer_list/initializer_list/
Last edited on
closed account (3hM2Nwbp)
I had thought of providing an initializer list, but it seemed to go against the grain in many cases. I value consistency more than anything (which is generally believed to be object construction by parenthesis). The same thing irks me about accessing a 2D array obfuscated by the use of operator().

Thanks for an elegant workaround.
Topic archived. No new replies allowed.