cannot instantiate a string literal

Jul 28, 2024 at 12:29am
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!!

?
Jul 28, 2024 at 9:57am
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 Jul 28, 2024 at 9:58am
Jul 28, 2024 at 10:33am
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 Jul 28, 2024 at 10:37am
Jul 28, 2024 at 10:53am
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 Jul 28, 2024 at 11:04am
Jul 28, 2024 at 12:25pm
What is CTAD?
Jul 28, 2024 at 12:31pm
Last edited on Jul 28, 2024 at 12:36pm
Jul 29, 2024 at 6:01pm
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");
Jul 29, 2024 at 7:54pm
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?


Jul 29, 2024 at 8:21pm
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?

Jul 30, 2024 at 9:37am
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 Jul 30, 2024 at 9:43am
Jul 30, 2024 at 12:45pm
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 Jul 30, 2024 at 5:29pm
Jul 31, 2024 at 6:52am
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.
Jul 31, 2024 at 3:05pm
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 Jul 31, 2024 at 3:07pm
Jul 31, 2024 at 4:16pm
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 Jul 31, 2024 at 4:30pm
Topic archived. No new replies allowed.