Deduce template parameter of class member from constructor of class - c++

I have a class A which contains a templated member B whose exact type should be deduced from A's constructor. The way this is supposed to work is that, as shown in the below example, B can be instantiated with either 1 or 2 parameters to its constructor (deduction guide will tell) but will take a const char* in any case. When I instantiate A with the const char* argument for the constructor, an object B should be instantiated from the const char* as A only takes a B object. However, this is how far I get:
#include <iostream>
template <bool LengthOpt>
struct B
{
B(const char*) { }
B(const char*, size_t) { }
void print() {
if constexpr (LengthOpt) {
std::cout << "LengthOpt is set" << std::endl;
}
}
};
B(const char*) -> B<false>;
B(const char*, size_t) -> B<true>;
template <template <bool LengthOpt> class T>
struct A
{
A(T is) : is_{is} {
}
void print() {
is_.print();
}
T is_;
};
int main()
{
A a("hello");
a.print();
}
And it yields those errors:
<source>:24:7: error: use of template template parameter 'T' requires template arguments; argument deduction not allowed in function prototype
A(T is) : is_{is} {
^
<source>:21:43: note: template is declared here
template <template <bool LengthOpt> class T>
^
<source>:32:5: error: use of template template parameter 'T' requires template arguments; argument deduction not allowed in non-static struct member
T is_;
^
<source>:21:43: note: template is declared here
template <template <bool LengthOpt> class T>
^
<source>:37:7: error: no viable constructor or deduction guide for deduction of template arguments of 'A'
A a("hello");
^
<source>:22:8: note: candidate template ignored: could not match 'A<T>' against 'const char *'
struct A
^
<source>:22:8: note: candidate function template not viable: requires 0 arguments, but 1 was provided
My take on the problem is that the compiler doesn't know that I want to instantiate an object B in A's constructor, as the template template argument specifies nothing. It could very well be just any object that takes one template parameter.
I'm scratching my head right now on how to resolve this. Is it even possible or am I scratching a limitation in C++ again?

Related

Why does an optional argument in a template constructor for enable_if help the compiler to deduce the template parameter? [duplicate]

This question already has answers here:
SFINAE: std::enable_if as function argument
(2 answers)
Closed 10 months ago.
The minimal example is rather short:
#include <iostream>
#include <array>
#include <type_traits>
struct Foo{
//template <class C>
//Foo(C col, typename std::enable_if<true,C>::type* = 0){
// std::cout << "optional argument constructor works" << std::endl;
//}
template <class C>
Foo(typename std::enable_if<true, C>::type col){
std::cout << "no optional argument constructor works NOT" << std::endl;
}
};
int main()
{
auto foo = Foo(std::array<bool,3>{0,0,1});
}
The first constructor works as expected. However the second constructor does not compile and I get
error: no matching function for call to ‘Foo::Foo(std::array)’
However the given explanation
note: template argument deduction/substitution failed
does not help, as std::enable_if<true, C>::type should be C and such the first argument in both constructors should look exactly the same to the compiler. I'm clearly missing something. Why is the compiler behaving differently and are there any other solution for constructors and enable_if, which do not use an optional argument?
Complete error message:
main.cpp:18:45: error: no matching function for call to ‘Foo::Foo(std::array)’
18 | auto foo = Foo(std::array<bool,3>{0,0,1});
| ^
main.cpp:11:5: note: candidate: ‘template Foo::Foo(typename std::enable_if::type)’
11 | Foo(typename std::enable_if<true, C>::type col){
| ^~~
main.cpp:11:5: note: template argument deduction/substitution failed:
main.cpp:18:45: note: couldn’t deduce template parameter ‘C’
18 | auto foo = Foo(std::array<bool,3>{0,0,1});
| ^
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(const Foo&)’
5 | struct Foo{
| ^~~
main.cpp:5:8: note: no known conversion for argument 1 from ‘std::array’ to ‘const Foo&’
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(Foo&&)’
main.cpp:5:8: note: no known conversion for argument 1 from ‘std::array’ to ‘Foo&&’
Template argument deduction does not work this way.
Suppose you have a template and a function using a type alias of that template:
template <typename T>
struct foo;
template <typename S>
void bar(foo<S>::type x) {}
When you call the function, eg foo(1) then the compiler will not try all instantiations of foo to see if any has a type that matches the type of 1. And it cannot do that because foo::type is not necessarily unambiguous. It could be that different instantiations have the same foo<T>::type:
template <>
struct foo<int> { using type = int; };
template <>
struct foo<double> { using type = int; };
Instead of even attempting this route and potentially resulting in ambiguity, foo<S>::type x is a nondeduced context. For details see What is a nondeduced context?.
Template argument deduction fails because C appears in a Non-deduced context. The linked page lists
The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id
as a non-deduced context.
They also mention another example further down:
For example, in A<T>::B<T2>, T is non-deduced because of rule #1 (nested name specifier), and T2 is non-deduced because it is part of the same type name, but in void(*f)(typename A<T>::B, A<T>), the T in A<T>::B is non-deduced (because of the same rule), while the T in A<T> is deduced.
The other answers already explain why argument deduction did not work here. If you want to enable_if your constructor, you can simply put the condition in the template list like this:
struct Foo{
// your condition here ---v
template <class C, typename std::enable_if_t< true >* = nullptr>
Foo(C col) {
std::cout << "constructor" << std::endl;
}
};

How to specify constructor's template arguments inside a new expression?

This issue occurred to me from an other piece of code but it boils down to this snippet:
#include <iostream>
struct A
{
template <int I>
A() : _i{I} {}
int _i;
};
int main()
{
A* ptr = new A; // how to call the constructor with a specific template argument ?
return 0;
}
This will not surprisingly raise the following error:
clang++ -std=c++17 -Wall main.cpp && ./a.out;
main.cpp:13:18: error: no matching constructor for initialization of 'A'
A* ptr = new A; // how to call the constructor with a specific template argument ?
^
main.cpp:6:5: note: candidate template ignored: couldn't infer template argument 'I'
A() : _i{I} {}
^
main.cpp:3:8: note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 0 were provided
struct A
^
This looks like an issue that would have been encountered a thousand times before but I couldn't find the solution on cppreference or SO.
How to specify constructor's template arguments inside a new expression ?
Unfortunately, you can't specify template arguments explicitly for constructor template, which can't be used unless the template parameters could be deduced. [temp.arg.explicit]/8
[ Note: Because the explicit template argument list follows the function template name, and because constructor templates ([class.ctor]) are named without using a function name ([class.qual]), there is no way to provide an explicit template argument list for these function templates. — end note ]
You have to deduce it. You cannot pass them explicitly.
One solution for your example would be:
struct A
{
template <int I>
A(std::integral_constant<int, I>) : _i{I} {}
int _i;
};
auto a = A{std::integral_constant<int, 4>{}};
As mentioned in my comment, a possible workaround could be to use inheritance:
struct A
{
int _i;
};
template<int I>
struct B : A
{
B() : A::_i(I) {}
};
...
A* a = new B<10>;

Nested Initializer_list template as function parameters [duplicate]

I don't understand why T cannot be deduced in this scenario:
template<class T>
class MyType
{
T * data;
};
class MyOtherType
{
};
template<typename T>
struct MyType_OutArg
{
typedef MyType<T> & type;
};
template<typename T>
void
DoSomething(typename MyType_OutArg<T>::type obj)
{
}
void func(MyType_OutArg<MyOtherType>::type obj)
{
DoSomething(obj);
}
From GCC 4.7.1 with -std=c++14
<source>: In function 'void func(MyType_OutArg<MyOtherType>::type)':
26 : <source>:26:20: error: no matching function for call to 'DoSomething(MyType<MyOtherType>&)'
DoSomething(obj);
^
26 : <source>:26:20: note: candidate is:
19 : <source>:19:1: note: template<class T> void DoSomething(typename MyType_OutArg<T>::type)
DoSomething(typename MyType_OutArg<T>::type obj)
^
19 : <source>:19:1: note: template argument deduction/substitution failed:
26 : <source>:26:20: note: couldn't deduce template parameter 'T'
DoSomething(obj);
^
Compiler returned: 1
Of course the following works:
DoSomething<MyOtherType>(obj);
but i'm unsure why it's necessary. Shouldn't the compiler have enough information?
This is because your case is a Non-deduced contexts.
Cited from http://en.cppreference.com/w/cpp/language/template_argument_deduction:
Non-deduced contexts
In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.
1) The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id
In your case, typename MyType_OutArg<T>::type will not participate in type deduction, and T is not known from elsewhere, thus this template function is ignored.

Template parameter cannot be deduced

I don't understand why T cannot be deduced in this scenario:
template<class T>
class MyType
{
T * data;
};
class MyOtherType
{
};
template<typename T>
struct MyType_OutArg
{
typedef MyType<T> & type;
};
template<typename T>
void
DoSomething(typename MyType_OutArg<T>::type obj)
{
}
void func(MyType_OutArg<MyOtherType>::type obj)
{
DoSomething(obj);
}
From GCC 4.7.1 with -std=c++14
<source>: In function 'void func(MyType_OutArg<MyOtherType>::type)':
26 : <source>:26:20: error: no matching function for call to 'DoSomething(MyType<MyOtherType>&)'
DoSomething(obj);
^
26 : <source>:26:20: note: candidate is:
19 : <source>:19:1: note: template<class T> void DoSomething(typename MyType_OutArg<T>::type)
DoSomething(typename MyType_OutArg<T>::type obj)
^
19 : <source>:19:1: note: template argument deduction/substitution failed:
26 : <source>:26:20: note: couldn't deduce template parameter 'T'
DoSomething(obj);
^
Compiler returned: 1
Of course the following works:
DoSomething<MyOtherType>(obj);
but i'm unsure why it's necessary. Shouldn't the compiler have enough information?
This is because your case is a Non-deduced contexts.
Cited from http://en.cppreference.com/w/cpp/language/template_argument_deduction:
Non-deduced contexts
In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.
1) The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id
In your case, typename MyType_OutArg<T>::type will not participate in type deduction, and T is not known from elsewhere, thus this template function is ignored.

In C++ template copy assignment operator not compatible with initializer_list?

Consider I have such code:
#include <initializer_list>
class my_class
{
public:
my_class() {}
void operator = (const std::initializer_list<int>&) {} // OK
template<typename ValueType> void operator = (const ValueType&) {} // Failed
};
int main(int argc, char* argv[])
{
my_class instance;
instance = {1, 2};
return 0;
}
The first copy assignment operator could be compiled OK with instance = {1, 2}. However, the template version would failed with such error:
code.cpp:15:14: error: no viable overloaded '='
instance = {1, 2};
~~~~~~~~ ^ ~~~~~~
code.cpp:3:7: note: candidate function (the implicit copy assignment operator) not viable: cannot convert initializer list argument to 'const my_class'
class my_class
^
code.cpp:3:7: note: candidate function (the implicit move assignment operator) not viable: cannot convert initializer list argument to 'my_class'
class my_class
^
code.cpp:9:39: note: candidate template ignored: couldn't infer template argument 'ValueType'
template<typename ValueType> void operator = (const ValueType&) {}
Why the template version is not compatible with the initializer_list?
Because initializer list is a non-deduced context. From [temp.deduct.type]:
The non-deduced contexts are:
— [...]
— A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter
does not have a type for which deduction from an initializer list is specified (14.8.2.1). [ Example:
template<class T> void g(T);
g({1,2,3}); // error: no argument deduced for T
—end example ]
There are some cases, however, where you can still pass in an initializer list to a template. From [temp.deduct.call]
Template argument deduction is done by comparing each function template parameter type (call it P) with
the type of the corresponding argument of the call (call it A) as described below. If P is a dependent type,
removing references and cv-qualifiers from P gives std::initializer_list<P'> or P'[N] for some P' and
N and the argument is a non-empty initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as
its argument, and in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list.
The examples that follow illustrate the cases in which this works:
template<class T> void f(std::initializer_list<T>);
f({1,2,3}); // T deduced to int
template<class T, int N> void h(T const(&)[N]);
h({1,2,3}); // T deduced to int, N deduced to 3
template<class T> void j(T const(&)[3]);
j({42}); // T deduced to int, array bound not considered
So in your specific case, you could do something like:
template <typename T>
void operator=(std::initializer_list<T> ) { }
Or:
template <typename T, size_t N>
void operator=(T const(&)[N]) { }
Although the latter apparently doesn't compile on clang, incorrectly.
Change your template version to
template <typename ValueType>
void operator =(const std::initializer_list<ValueType> &) {}