cannot instantiate a string literal

I have this code:

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
39
40
41
42
43
44
45
template<typename ...T>
struct Group;

template<typename T1>
struct Group<T1>
{
	T1 t1_;
	Group() = default;

	[[nodiscard]] explicit Group(const T1& t1)
		: t1_(t1)
	{
	}

	[[nodiscard]] explicit Group(T1&& t1)
		: t1_(std::move(t1))
	{
	}
	explicit operator const T1& () const { return t1_; }
	explicit operator T1& () { return t1_; }
};

template<typename T1, typename ... T>  
struct Group<T1, T...> : Group<T...>  
{
	T1 t1_;

	[[nodiscard]] explicit Group(const T1& t1, T&& ...t) : Group<T...>(std::forward<T>(t)...),
		t1_(t1)
	{
	}
	[[nodiscard]] explicit Group(T1&& t1, T&& ...t) : Group<T...>(std::forward<T>(t)...),
		t1_(std::move(t1))
	{
	}
	explicit operator const T1& () const { return t1_; }
	explicit operator T1& () { return t1_; }

};

template<typename ...T>
auto makeGroup(T&&...  t)
{
	return Group<T...>(std::forward<T>(t)...);
}


and I call it like this:

1
2
3
4
void useGroup()
{
    auto gg = makeGroup(3, 2.2,  "hello");
}


I get an error: Group<const char (&)[6]>::Group(T1)': member function already defined or declared


What is going on? the type array of const char is getting rejected....


if however I change the code to:

 
	auto g = makeGroup(3, 2.2, (const char*)"hello");


The error goes away!!

?
It seems like when T1 is const char(&)[6] both constructors ends up having the same signature Group(const char(& t1)[6]) (because of the reference collapsing rules) and this is an error because you're only allowed to define the same constructor once.
Last edited on
You might want to only define one (templated) version of Group that uses forwarding references and std::forward.

In that case, T1 can be a reference so you will have to use std::remove_reference to get the non-referenced type.

You can also simplify the implementation by implementing the base case without any template parameters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<typename ...T>
struct Group;

template<>
struct Group<>
{
};

template<typename T1, typename ... T>  
struct Group<T1, T...> : Group<T...>  
{
	std::remove_reference_t<T1> t1_;

	template<typename U1, typename ... U> 
	[[nodiscard]] explicit Group(U1&& t1, U&& ...t)
	:	Group<U...>(std::forward<U>(t)...),
		t1_(std::forward<U1>(t1))
	{
	}

	explicit operator const std::remove_reference_t<T1>& () const { return t1_; }
	explicit operator std::remove_reference_t<T1>& () { return t1_; }
};
Last edited on
Or maybe the following is better...

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
template<typename ...T>
struct Group;

template<>
struct Group<>
{
};

template<typename T1, typename ... T>  
struct Group<T1, T...> : Group<T...>  
{
	T1 t1_;

	template<typename U1, typename ... U> 
	[[nodiscard]] explicit Group(U1&& t1, U&& ...t)
	:	Group<T...>(std::forward<U>(t)...),
		t1_(std::forward<U1>(t1))
	{
	}

	explicit operator const T1& () const { return t1_; }
	explicit operator T1& () { return t1_; }
};

template<typename ...T>
auto makeGroup(T&&...  t)
{
	return Group<std::remove_reference_t<T>...>(std::forward<T>(t)...);
}

This way you don't need to use std::remove_reference inside Group. Instead std::remove_reference is used inside makeGroup. You can store references in Group if you want by constructing it directly without using makeGroup. I think this is more similar to how std::tuple/std::make_tuple works, although std::tuple also uses deduction guides to make CTAD prefer non-reference types which is something that you might also want to do.
Last edited on
What is CTAD?
Last edited on
Unfortunately it does not compile with your code Peter87

I get


error C2440: 'initializing': cannot convert from 'const char [6]' to 'const char'


when using this code:

 
auto g = makeGroup(3,  2.2, "hello");
The complete code to reproduce is


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
template<typename ...T>
struct Group;

template<>
struct Group<>
{
};

template<typename T1, typename ... T>
struct Group<T1, T...> : Group<T...>
{
	T1 t1_;
	template<typename U1, typename ... U>
	[[nodiscard]] explicit Group(U1&& t1, U&& ...t)
		: Group<std::remove_reference_t<U>...>(std::forward<U>(t)...),
			t1_(std::forward<U1>(t1))
		{
		}

		explicit operator const T1& () const { return t1_; }
		explicit operator T1& () { return t1_; }
	};


template<typename ...T>
auto makeGroup(T&&...  t)
{
	return Group<std::remove_reference_t<T>...>(std::forward<T>(t)...);
}



the call can be very simple:

 
auto g = makeGroup("hello");



The error is


error C2440: 'initializing': cannot convert from 'const char [6]' to 'const char'


Why??? Where did the extent [6] go?


The problem can be reduced to its bare essentials:

See the new reduced code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template<typename T>
struct Group
{
	T t;
	template<typename U>
	explicit Group(U&& t) : t{t} {}
};

template<typename T>
auto makeGroup(T&& t)
{
	return Group<std::remove_reference_t<T>>(std::forward<T>(t));
}

void useGroup()
{
	auto g = makeGroup("Hello");
}


The error is the same:



error C2440: 'initializing': cannot convert from 'const char [6]' to 'const char'


Why??

where did the extent ([6]) go?

The error is because you cannot initialize an array like that.

I guess I changed the third parameter to std::string when I tested this code because I wanted to see if "perfect forwarding" was still working.

The following code seems to work even with string literals (if you're fine with them being stored as const char*):

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
template<typename ...T>
struct Group;

template<>
struct Group<>
{
};

template<typename T1, typename ... T>
struct Group<T1, T...> : Group<T...>
{
	T1 t1_;
	template<typename U1, typename ... U>
	[[nodiscard]] explicit Group(U1&& t1, U&& ...t)
	:	Group<std::unwrap_ref_decay_t<U>...>(std::forward<U>(t)...),
		t1_(std::forward<U1>(t1))
	{
	}

	explicit operator const T1& () const { return t1_; }
	explicit operator T1& () { return t1_; }
};


template<typename ...T>
auto makeGroup(T&&...  t)
{
	return Group<std::unwrap_ref_decay_t<T>...>(std::forward<T>(t)...);
}

I got the idea of using unwrap_ref_decay from here:
https://en.cppreference.com/w/cpp/utility/tuple/make_tuple#Possible_implementation
Last edited on
but why does the error say
cannot convert from 'const char [6]' to 'const char'
?

const char[6] ==> an array of const char
const char ==> a const char


what happened with the [6]?
Last edited on
You can initialize arrays like this:

 
char arr[6]{'a', 'b', 'c'};

Since C++20 you can also use normal parentheses:

 
char arr[6]('a', 'b', 'c');

That is why it expects a char.
Since C++20 you can also use normal parentheses:
char arr[6]('a', 'b', 'c');
Sorry to derail this thread, but why would they add another syntax complication like that as a feature? What problem is that solving? Isn't the syntax of this language complicated enough?

As you can tell, I am very much not up-to-date on the latest C++20/23 stuff, embarrassingly so.
Last edited on
To allow std::make_unique and similar functions to initialize aggregates.

Example:
1
2
3
4
5
6
7
struct Point
{
    int x;
    int y;
};

auto p = std::make_unique<Point>(1, 2);

This code did not work in C++17 because std::make_unique uses parentheses to initialize the object but Point is an aggregate and had to be initialized using curly brackets.
Last edited on
Topic archived. No new replies allowed.