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:
Without a constructor, you need some other way to guide deduction. That's what this is there for, and it allows: