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>;
Related
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?
I have a pretty large class which is reduced to a minimum failing example below:
#include <vector>
template <typename T> class Base {
public:
Base(std::vector<T> &&other) : map{other} {}
private:
const std::vector<T> map;
};
template <typename T> class Derived : public Base<T> {
public:
Derived(std::vector<T> &&other) : Base<T>{other} {}
};
int main() {
Derived<double>(std::vector<double>{1,2,3});
}
When I run this, I get
$ clang++ -std=c++17 -O3 main.cpp && ./a.out
main.cpp:13:37: error: no matching constructor for initialization of 'Base<double>'
Derived(std::vector<T> &&other) : Base<T>{other} {}
^ ~~~~~~~
main.cpp:17:3: note: in instantiation of member function 'Derived<double>::Derived' requested here
Derived<double>(std::vector<double>{1,2,3});
^
main.cpp:3:29: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'std::vector<double>' to 'const Base<double>' for 1st argument
template <typename T> class Base {
^
main.cpp:3:29: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'std::vector<double>' to 'Base<double>' for 1st argument
template <typename T> class Base {
^
main.cpp:5:3: note: candidate constructor not viable: no known conversion from 'std::vector<double>' to 'std::vector<double> &&' for 1st argument
Base(std::vector<T> &&other) : map{other} {}
^
1 error generated.
I don't understand why this is giving me a compilation error. I expect Base<T>{other} on line 13 to call the constructor on line 5 since I am passing in other which is declared in Derived's constructor argument to be an rvalue (std::vector<T> && other). However, the compiler says that it is an lvalue (std::vector<double>).
since I am passing in other which is declared in Derived's constructor argument to be an rvalue (std::vector && other)
Actually, in the scope of Derived(std::vector<T> &&other), other is an lvalue, with a type that is rvalue reference. This is a common point of confusion. Behold the mighty cppreference:
The following expressions are lvalue expressions:
the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
You'll want to explicitly cast other to rvalue via std::move() in Derived's constructor:
template <typename T> class Derived : public Base<T> {
public:
Derived(std::vector<T> &&other) : Base<T>{std::move(other)} {}
};
Background:
C++17 has two great features: aggregate initialization and template type deduction (for classes). Aggregate initialization allows you to instantiate fields without copying or moving them, and template type deduction makes it so that you don't have to specify the type of your argument.
The Wrapper class in the below code is an example of this. As long as HAVE_MOVE_AND_COPY is left undefined, it has aggregate initialization, but then template type deduction doesn't work.
On the other hand, if HAVE_MOVE_AND_COPY is defined, then template type deduction works, but aggregate initialization breaks. How can I have both?
#include <cstdio>
#include <utility>
template<class T>
struct Wrapper {
T value;
#ifdef HAVE_MOVE_AND_COPY
Wrapper(T const & val) : value{val} {}
Wrapper(T && val) : value{std::move(val)} {}
#endif
Wrapper(Wrapper const &) = default;
Wrapper(Wrapper &&) = default;
};
struct VocalClass {
VocalClass() { puts("VocalClass()"); }
VocalClass(VocalClass const&) { puts("VocalClass(VocalClass const &)"); }
VocalClass(VocalClass &&) { puts("VocalClass(VocalClass &&)"); }
};
int main() {
Wrapper<VocalClass> w { VocalClass() };
#ifdef TRY_DEDUCTION
Wrapper w2 { VocalClass() };
#endif
}
Example:
No moving or copying occurs, but you don't have template deduction:
$ c++ -std=c++17 example.cc && ./a.out
VocalClass()
Has template deduction, but VocalClass gets moved:
$ c++ -DHAVE_MOVE_AND_COPY -DTRY_DEDUCTION -std=c++17 example.cc && ./a.out
VocalClass()
VocalClass(VocalClass &&)
VocalClass()
VocalClass(VocalClass &&)
Without HAVE_MOVE_AND_COPY, template type deduction breaks:
sky#sunrise:~$ c++ -DTRY_DEDUCTION -std=c++17 example.cc && ./a.out
example.cc: In function ‘int main()’:
example.cc:27:31: error: class template argument deduction failed:
Wrapper w2 { VocalClass() };
^
example.cc:27:31: error: no matching function for call to ‘Wrapper(VocalClass)’
example.cc:12:5: note: candidate: ‘template<class T> Wrapper(Wrapper<T>&&)-> Wrapper<T>’
Wrapper(Wrapper &&) = default;
^~~~~~~
example.cc:12:5: note: template argument deduction/substitution failed:
example.cc:27:31: note: ‘VocalClass’ is not derived from ‘Wrapper<T>’
Wrapper w2 { VocalClass() };
^
example.cc:11:5: note: candidate: ‘template<class T> Wrapper(const Wrapper<T>&)-> Wrapper<T>’
Wrapper(Wrapper const &) = default;
^~~~~~~
example.cc:11:5: note: template argument deduction/substitution failed:
example.cc:27:31: note: ‘VocalClass’ is not derived from ‘const Wrapper<T>’
Wrapper w2 { VocalClass() };
^
example.cc:5:8: note: candidate: ‘template<class T> Wrapper(Wrapper<T>)-> Wrapper<T>’
struct Wrapper {
^~~~~~~
example.cc:5:8: note: template argument deduction/substitution failed:
example.cc:27:31: note: ‘VocalClass’ is not derived from ‘Wrapper<T>’
Wrapper w2 { VocalClass() };
Question
Is there any way I can have both template type deduction, and aggregate initialization?
First, the term is "class template argument deduction."
Second, what you need is a deduction guide:
template<class T>
struct Wrapper {
T value;
};
template <typename T>
Wrapper(T) -> Wrapper<T>; // this is a deduction guide
Without a constructor, you need some other way to guide deduction. That's what this is there for, and it allows:
Wrapper w{4}; // ok, Wrapper<int>
You have a misunderstanding of what the term "aggregate" means.
Firstly, what you are doing is called list-initialization. List-initialization will only aggregate-initialize your instance if the type of the instance is an aggregate. Aggregate-initialization allows you to initialize the base classes and/or members of your class in order with an initializer list.
From cppreference:
An aggregate is one of the following types:
array type
class type (typically, struct or union), that has
no user-provided, inherited, or explicit constructors (explicitly defaulted or deleted constructors are allowed)
no virtual, private, or protected (since C++17) base classes
no virtual member functions
no default member initializers
The second bullet-point applies here. Since you have a user-provided constructor (a constructor written out by the user instead of generated by the compiler) when HAVE_MOVE_AND_COPY is defined, your type is not an aggregate and the compiler will only look for constructors to initialize your instance.
Barry covers the rest about how to make an aggregate with class template argument deduction.
I need to make a templated class constructor take a dependent type (on the templated class types). This works fine, unless I have a specialization of the templated class, in which case the constructor doesn't seem to be found. And if I reimplement the constructor in the specialized subclass, I don't seem to be able to initialize the base class either through the constructor or directly.
Is there any way to preserve this relatively narrow interface outside of the class?
class T1 {};
class T2 {};
// KeyType
template <typename SELECT>
class KeyType {
};
// Declarations
template <typename SELECT = T1>
class TestTemplate {
protected:
TestTemplate() {}
KeyType<SELECT> k;
public:
TestTemplate(KeyType<SELECT> const &key) : k(key) {}
};
template <>
class TestTemplate<T2> : public TestTemplate<T1> {
};
int main() {
KeyType<T2> key;
TestTemplate<T2> foo(key);
return 0;
}
After staring at it for a bit, I realize that the problem is that I can't just arbitrarily convert the KeyType<T2> to a KeyType<T1> for the TestTemplate<T1> baseclass.
g++ gives:
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:27:29: error: no matching function for call to 'TestTemplate<T2>::TestTemplate(KeyType<T2>&)'
TestTemplate<T2> foo(key);
^
main.cpp:20:7: note: candidate: TestTemplate<T2>::TestTemplate()
class TestTemplate<T2> : public TestTemplate<T1> {
^
main.cpp:20:7: note: candidate expects 0 arguments, 1 provided
main.cpp:20:7: note: candidate: constexpr TestTemplate<T2>::TestTemplate(const TestTemplate<T2>&)
main.cpp:20:7: note: no known conversion for argument 1 from 'KeyType<T2>' to 'const TestTemplate<T2>&'
main.cpp:20:7: note: candidate: constexpr TestTemplate<T2>::TestTemplate(TestTemplate<T2>&&)
main.cpp:20:7: note: no known conversion for argument 1 from 'KeyType<T2>' to 'TestTemplate<T2>&&'
Constructors are not inherited at all in C++, so your instance TestTemplate<T2> only has the implicitly declared constructors (default, copy and move it seems by the error messages you posted). In case you assume that, when you specialize a template, the specialized template inherits declarations and definitions from the template that is being specialized: that is not the case. You have to redeclare and redefine all members in your specialized template again.
So in your case, you have to add the appropriate constructor to your specialized template like so:
template <>
class TestTemplate<T2> : public TestTemplate<T1> {
public:
TestTemplate<KeyType<T2> const &key) :
TestTemplate<T1>(...) {}
};
Since the base class, TestTemplate<T1>, does not provide a default constructor, you have to call that constructor, however, it is unclear what you want to pass to it as the key argument, since you have a reference to an instance of KeyType<T2> and not KeyType<T1>.
If you do wish to inherit the constructor from TestTemplate<T1>, you can do this with a using directive, assuming you're using C++11:
template <>
class TestTemplate<T2> : public TestTemplate<T1> {
public:
using TestTemplate<T1>::TestTemplate;
};
But admittedly, I'm not 100% about the syntax here.
template<typename T>
class A {
public:
A(T b) : a(b) {
}
private:
T a;
};
A object(12); //Why does it give an error?
Why can't the type T be deduced from the argument 12 automatically?
Template argument deduction applies only to function and member function templates but not to class templates. So your code is ill-formed.
You need to provide the template argument explicitly.
A<int> object(12); //fine