Have both Aggregate Initialization and Template Deduction - c++

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.

Related

Derived template class can't find move constructor for base template class

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)} {}
};

Perfect forwarding interpreted as rvalue reference

My understanding was that ConcreteType&& was an rvalue and TemplateType&& was "perfect forwarding".
I'm trying to use perfect forwarding, but clang-tidy interprets it as rvalue reference.
clang and gcc don't complain, but clang-tidy marks it as parse error before doing any further analysis, so I'm not sure if it's an issue with the code or with clang-tidy.
I suspect it's related to the fact I'm using it in a constructor, but I'm not sure.
Minimum code:
#include <memory>
template <typename T>
class my_wrapper {
T val;
public:
my_wrapper(T&& val_)
: val{std::forward<T>(val_)} {}
};
struct my_struct {
int x;
};
auto xyz() {
my_struct ms{3};
return my_wrapper<my_struct>(ms);
}
Error message:
Error while processing /path/foo.cpp.
/path/foo.cpp:20:25: error: no matching constructor for initialization of 'my_wrapper<my_struct>' [clang-diagnostic-error]
my_wrapper<my_struct> wrap(ms);
^
/path/foo.cpp:5:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'my_struct' to 'const my_wrapper<my_struct>' for 1st argument
class my_wrapper {
^
/path/foo.cpp:5:7: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'my_struct' to 'my_wrapper<my_struct>' for 1st argument
/path/foo.cpp:9:3: note: candidate constructor not viable: no known conversion from 'my_struct' to 'my_struct &&' for 1st argument
my_wrapper(T&& val_)
^
T is fully resolved by the time the constructor is invoked, so it's not perfect forwarding anymore
If you want perfect forwarding on a function, it has to be the function itself that's templated, not the enclosing class.
Like so:
template <typename T>
class my_wrapper {
T val;
public:
template<typename U>
my_wrapper(U&& val_)
: val{std::forward<U>(val_)} {}
};

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>;

Specialized template class (descended from general case) with a template constructor using a dependent type

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.

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> &) {}