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_)} {}
};
Related
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.
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> &) {}
In the following complete test case, if I use the first ctor taking a functor by value and moving it into place then the code compiles and works as expected.
However if I use the universal reference ctor it fails to compile (I've included the clang error message below).
How can I fix this or what am I doing wrong?
#include <functional>
#include <utility>
#include <exception>
template<typename F>
struct py_catch {
F func;
/*
//Works
py_catch(F f)
: func ( std::move(f) )
{ } */
//Doesn't
template<typename F2>
py_catch(F2&& f)
: func ( std::forward<F2>(f) )
{ }
py_catch(py_catch&&)=default;
py_catch(const py_catch&)=default;
py_catch& operator=(const py_catch&)=default;
py_catch& operator=(py_catch&&)=default;
template<typename... Args>
auto operator()(Args&&... args)
-> decltype(func(std::forward<Args>(args)...)) {
try {
return func(std::forward<Args>(args)...);
}
catch(const std::exception&) {
throw;
}
}
};
template<typename F>
py_catch<typename std::remove_reference<F>::type> make_py_catch(F&& f) {
return py_catch<typename std::remove_reference<F>::type>(std::forward<F>(f));
}
int main() {
std::function<void()> s;
s = make_py_catch([]{});
}
Compile error:
testcase2.cpp:16:7: error: no matching constructor for initialization of '<lambda at testcase2.cpp:43:23>'
: func ( std::forward<F2>(f) )
^ ~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:1764:10: note: in instantiation of function template specialization 'py_catch<<lambda at testcase2.cpp:43:23> >::py_catch<py_catch<<lambda at testcase2.cpp:43:23> > &>' requested here
new _Functor(*__source._M_access<_Functor*>());
^
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:1799:8: note: in instantiation of member function 'std::_Function_base::_Base_manager<py_catch<<lambda at testcase2.cpp:43:23> > >::_M_clone' requested here
_M_clone(__dest, __source, _Local_storage());
^
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:2298:33: note: in instantiation of member function 'std::_Function_base::_Base_manager<py_catch<<lambda at testcase2.cpp:43:23> > >::_M_manager' requested here
_M_manager = &_My_handler::_M_manager;
^
/usr/lib/gcc/x86_64-unknown-linux-gnu/4.7.2/../../../../include/c++/4.7.2/functional:2173:4: note: in instantiation of function template specialization 'std::function<void ()>::function<py_catch<<lambda at testcase2.cpp:43:23> > >' requested here
function(std::forward<_Functor>(__f)).swap(*this);
^
testcase2.cpp:43:7: note: in instantiation of function template specialization 'std::function<void ()>::operator=<py_catch<<lambda at testcase2.cpp:43:23> > >' requested here
s = make_py_catch([]{});
^
testcase2.cpp:43:23: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'py_catch<<lambda at testcase2.cpp:43:23> >' to 'const <lambda at testcase2.cpp:43:23>' for 1st argument
s = make_py_catch([]{});
^
testcase2.cpp:43:23: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'py_catch<<lambda at testcase2.cpp:43:23> >' to '<lambda at testcase2.cpp:43:23>' for 1st argument
s = make_py_catch([]{});
^
testcase2.cpp:43:23: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided
1 error generated.
I think the problem is your converting constructor template template<typename F2> py_catch(F2&&) is too greedy. Another way to trigger the error is:
int main() {
auto x( make_py_catch([]{}) );
auto y(x);
}
This copy-construction uses an lvalue of some type py_catch<..>. The copy-ctor expects a py_catch<..> const&, whereas your greedy template provides an overload with a parameter of type py_catch<..>&. A special rule in [over.ics.rank]/3 now says the overload taking the reference-to-less-qualified-type is preferred. Therefore, not the copy-ctor, but the constructor template is called, which tries to initialize the data member (lambda) using the whole py_catch<..> object (instead of its func member).
A simple, but possibly not optimal solution is to provide another copy-ctor for non-const lvalues py_catch(py_catch&) = default;. But then, when you use inheritance or user-defined conversions, the constructor template will still be preferred.
Another solution is to use some SFINAE on the constructor template; for example check for is_same, is_base_of or something similar (remember to remove_reference the possible reference from F2). is_convertible might work as well, but I suspect it would be recursively trying to use the constructor template to do its check.
Okay, I found a better fix, which hints at the actually problem.
I suspected the issue lay in the copy and move ctors for some reason choosing the template rather than the defaulted ctors. This meant that py_catch<lambda_types?> being passed to the template ctor and being forwarded to func.
So I added a test in the ctor using SFINAE and this fixed the issue as it will reject anything other than matching func types.
Like so:
template
<
typename F2,
typename =typename std::enable_if<std::is_same<F2, F>::value, F>::type
>
py_catch(F2&& f)
: func ( std::forward<F2>(f) )
{ }
Ugly yes.
I have to wait a few days before I can mark this correct, so if someone can tell me why it's not choosing the defaulted ctors over the template then I'll mark that correct.
Consider this case:
template<typename T>
struct A {
A(A ???&) = default;
A(A&&) { /* ... */ }
T t;
};
I explicitly declared a move constructor, so I need to explicitly declare a copy constructor if I want to have a non-deleted copy constructor. If I want to default it, how can I find out the correct parameter type?
A(A const&) = default; // or
A(A &) = default; // ?
I'm also interested in whether you encountered a case where such a scenario actually popped up in real programs. The spec says
A function that is explicitly defaulted shall ...
have the same declared function type (except for possibly differing ref-qualifiers and except that in
the case of a copy constructor or copy assignment operator, the parameter type may be "reference to non-const T", where T is the name of the member function’s class) as if it had been implicitly declared,
If the implicitly-declared copy constructor would have type A &, I want to have my copy constructor be explicitly defaulted with parameter type A &. But if the implicitly-declared copy constructor would have parameter type A const&, I do not want to have my explicitly defaulted copy constructor have parameter type A &, because that would forbid copying from const lvalues.
I cannot declare both versions, because that would violate the above rule for the case when the implicitly declared function would have parameter type A & and my explicitly defaulted declaration has parameter type A const&. From what I can see, a difference is only allowed when the implicit declaration would be A const&, and the explicit declaration would be A &.
Edit: In fact, the spec says even
If a function is explicitly defaulted on its first dec-
laration, ...
in the case of a copy constructor, move constructor, copy assignment operator, or move assignment operator, it shall have the same parameter type as if it had been implicitly declared.
So I need to define these out-of-class (which I think doesn't hurt, since as far as I can see the only difference is that the function will become non-trivial, which is likely anyway in those cases)
template<typename T>
struct A {
A(A &);
A(A const&);
A(A&&) { /* ... */ }
T t;
};
// valid!?
template<typename T> A<T>::A(A const&) = default;
template<typename T> A<T>::A(A &) = default;
Alright I found that it is invalid if the explicitly declared function is A const&, while the implicit declaration would be A &:
A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.
This matches what GCC is doing. Now, how can I achieve my original goal of matching the type of the implicitly declared constructor?
I guess I fail to see the problem... how does this differ from the common case of implementing a copy constructor?
If your copy constructor will not modify the argument (and the implicitly defined copy constructor will not do it) then the argument should be passed as a constant reference. The only use case I know of for a copy constructor that does not take the argument by constant reference is when in C++03 you want to implement moving a la std::auto_ptr which is usually a bad idea anyway. And in C++0x moving would be implemented as you have with a move constructor.
It seems to me that you would need some type deduction (concepts ?).
The compiler will generally use the A(A const&) version, unless it is required by one of the member that it is written A(A&). Therefore, we could wrap some little template hackery to check which version of a copy constructor each of the member has.
Latest
Consult it at ideone, or read the errors by Clang after the code snippet.
#include <memory>
#include <type_traits>
template <bool Value, typename C>
struct CopyConstructorImpl { typedef C const& type; };
template <typename C>
struct CopyConstructorImpl<false,C> { typedef C& type; };
template <typename C, typename T>
struct CopyConstructor {
typedef typename CopyConstructorImpl<std::is_constructible<T, T const&>::value, C>::type type;
};
// Usage
template <typename T>
struct Copyable {
typedef typename CopyConstructor<Copyable<T>, T>::type CopyType;
Copyable(): t() {}
Copyable(CopyType) = default;
T t;
};
int main() {
{
typedef Copyable<std::auto_ptr<int>> C;
C a; C const b;
C c(a); (void)c;
C d(b); (void)d; // 32
}
{
typedef Copyable<int> C;
C a; C const b;
C c(a); (void)c;
C d(b); (void)d;
}
}
Which gives:
6167745.cpp:32:11: error: no matching constructor for initialization of 'C' (aka 'Copyable<std::auto_ptr<int> >')
C d(b); (void)d;
^ ~
6167745.cpp:22:7: note: candidate constructor not viable: 1st argument ('const C' (aka 'const Copyable<std::auto_ptr<int> >')) would lose const qualifier
Copyable(CopyType) = default;
^
6167745.cpp:20:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Copyable(): t() {}
^
1 error generated.
Before Edition
Here is the best I could come up with:
#include <memory>
#include <type_traits>
// Usage
template <typename T>
struct Copyable
{
static bool constexpr CopyByConstRef = std::is_constructible<T, T const&>::value;
static bool constexpr CopyByRef = !CopyByConstRef && std::is_constructible<T, T&>::value;
Copyable(): t() {}
Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
Copyable(Copyable const& rhs, typename std::enable_if<CopyByConstRef>::type* = 0): t(rhs.t) {}
T t;
};
int main() {
{
typedef Copyable<std::auto_ptr<int>> C; // 21
C a; C const b; // 22
C c(a); (void)c; // 23
C d(b); (void)d; // 24
}
{
typedef Copyable<int> C; // 27
C a; C const b; // 28
C c(a); (void)c; // 29
C d(b); (void)d; // 30
}
}
Which almost works... except that I got some errors when building the "a".
6167745.cpp:14:78: error: no type named 'type' in 'std::enable_if<false, void>'
Copyable(Copyable const& rhs, typename std::enable_if<CopyByConstRef>::type* = 0): t(rhs.t) {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
6167745.cpp:22:11: note: in instantiation of template class 'Copyable<std::auto_ptr<int> >' requested here
C a; C const b;
^
And:
6167745.cpp:13:67: error: no type named 'type' in 'std::enable_if<false, void>'
Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
6167745.cpp:28:11: note: in instantiation of template class 'Copyable<int>' requested here
C a; C const b;
^
Both occurs for the same reason, and I do not understand why. It seems that the compiler tries to implement all constructors even though I have a default constructor. I would have thought that SFINAE would apply, but it seems it does not.
However, the error line 24 is correctly detected:
6167745.cpp:24:11: error: no matching constructor for initialization of 'C' (aka 'Copyable<std::auto_ptr<int> >')
C d(b); (void)d;
^ ~
6167745.cpp:13:7: note: candidate constructor not viable: 1st argument ('const C' (aka 'const Copyable<std::auto_ptr<int> >')) would lose const qualifier
Copyable(Copyable& rhs, typename std::enable_if<CopyByRef>::type* = 0): t(rhs.t) {}
^
6167745.cpp:11:7: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Copyable(): t() {}
^
Where we can see that the CopyByConstRef was correctly evicted from the overload set, hopefully thanks to SFINAE.
I've never seen a case where the implicit copy constructor would be A&- you should be good in any case with const A&.