I have the following code:
#include <iostream>
#include <typeinfo>
template <typename T>
struct A : T {
template <typename ...Args>
A(Args&&... params) : T(std::forward<Args>(params)...), x(0) {
std::cout << "Member 'x' was default constructed\n";
}
template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
A(O o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
std::cout << "Member 'x' was constructed from arguments\n";
}
int x;
};
struct B{
B(const char*) {}
};
int main() {
A<B> a("test");
A<B> y(3, "test");
return 0;
}
It works fine, and prints
Member 'x' was default constructed
Member 'x' was constructed from arguments
However, if the first argument of the second overload is a reference, suddenly the second overload is never taken, and compilation fails:
template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
A(O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
std::cout << "Member 'x' was constructed from arguments\n";
} // Note the O& in the arguments
Why is this? Is it possible to fix it and avoid copies?
EDIT: Using an universal reference apparently makes it work again. A const reference, which is what I'd actually like, does not work either.
In addition, even saving the input parameter into a separate value (avoiding an rvalue) will still not work:
int main() {
double x = 3.0;
A<B> y(x, "test"); // Still not working
return 0;
}
Why is this?
In case of the following declaration:
template <typename O>
A(O& o);
the call:
A{3};
deduces the O type to be int, hence you end up with the following instantiation:
A(int& o);
But what you are doing, is you are trying to bind an rvalue (which 3 certainly is) to this instantiated non-const lvalue reference, and this is not allowed.
Is it possible to fix it and avoid copies?
You can declare the o type to be a forwarding reference as well, and then forward it to the constructor of x (but for primitive types like int this is really not necessary at all):
template <typename O>
A(O&& o) : x{std::forward<O>(o)} {}
Alternatively, you can declare the constructor as taking a const lvalue reference (so that rvalues can be bound by it):
template <typename O>
A(const O& o) : x{o} {}
Using a universal reference fixes the problem, but a const reference (which is actually what I wanted) does not, unfortunately. In addition, even saving the input parameter into a separate value (avoiding an rvalue) will still not work.
This is because a universal reference almost always produces an exact match, and the first constructor taking universal references is the best viable function in the overload resolution procedure.
When passing an rvalue, the deduced int&& is a better match for rvalues than const int&.
When passing an lvalue, the deduced int& is a better match for non-const lvalues (like your variable x) than const int&.
Having said that, this greedy constructor taking universal references is in both cases the best viable function, because when instantiating:
template <typename... Args>
A(Args&&... params);
template <typename O, typename... Args>
A(const O& z, Args&&... params);
e.g. for the following call:
double x = 3.0;
A a(x, "test");
the compiler ends up with:
A(double&, const char (&)[5]);
A(const double&, const char (&)[5]);
where the first signature is a better match (no need to add a const qualification).
If for some reasons you really want to have this O type to be templated (now no matter if this will be a universal reference or a const lvalue reference), you have to disable the first greedy constructor from the overload resolution procedure if its first argument can be used to construct int (just like the second one is enabled under such conditions):
template <typename T>
struct A : T
{
template <typename Arg, typename... Args, typename = typename std::enable_if<!std::is_constructible<int, Arg>::value>::type>
A(Arg&& param, Args&&... params) : T(std::forward<Arg>(param), std::forward<Args>(params)...), x(0) {
std::cout << "Member 'x' was default constructed\n";
}
template <typename O, typename... Args, typename = typename std::enable_if<std::is_constructible<int, O>::value>::type>
A(const O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
std::cout << "Member 'x' was constructed from arguments\n";
}
int x;
};
DEMO
Related
I want to store passed data via constexpr constructor of a struct, and store the data in a std::tuple, to perform various TMP / compile time operations.
Implementation
template <typename... _Ts>
struct myInitializer {
std::tuple<_Ts...> init_data;
constexpr myInitializer(_Ts&&... _Vs)
: init_data{ std::tuple(std::forward<_Ts>(_Vs)...) }
{}
};
Stored data uses a lightweight strong type struct, generated via lvalue and rvalue helper overload:
template <typename T, typename... Ts>
struct data_of_t {
using type = T;
using data_t = std::tuple<Ts...>;
data_t data;
constexpr data_of_t(Ts&&... _vs)
: data(std::forward<Ts>(_vs)...)
{}
};
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... _vs) {
return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};
template<typename T, typename... Ts>
constexpr auto data_of(Ts&... _vs) {
return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};
It's implemented like
template <typename T = int>
class test {
public:
static constexpr auto func(int p0=0, int p1=1, int p2=3) noexcept {
return data_of <test<T>>
(data_of<test<T>>(p0, p1));
}
};
int main() {
constexpr // fails to run constexpr // works without
auto init = myInitializer (
test<int>::func()
,test<int>::func(3)
,test<int>::func(4,5)
);
std::apply([&](auto&&... args) {
//std::cout << __PRETTY_FUNCTION__ << std::endl;
auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
}
, init.init_data);
}
Getting to the point
std::tuple_cat fails if myInitializer instance is constexpr.
std::apply([&](auto&&... args) {
auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
It appears to be related to the const qualifier added via constexpr.
How can this be fixed?
See full example at https://godbolt.org/z/j5xdT39aE
This:
auto merged_tuple = std::tuple_cat(std::forward<decltype(args.data)>(args.data)...);
is not the right way to forward data. decltype(args.data) is going to give you the type of that data member - which is not a function of either the const-ness or value category of args. Let's take a simpler example:
void f(auto&& arg) {
g(std::forward<decltype(arg.data)>(arg.data));
}
struct C { int data; };
C c1{1};
const C c2{2};
f(c1);
f(c2);
f(C{3});
So here I have three calls to f (which call f<C&>, f<const C&>, and f<C>, respectively). In all three cases, decltype(arg.data) is... just int. That's what the type of C::data is. But that's not how it needs to be forwarded (it won't compile for c2 because we're trying to cast away const-ness -- as in your example -- and it'll erroneously move out of c1).
What you want is to forward arg, separately, and then access data:
void f(auto&& arg) {
g(std::forward<decltype(arg)>(arg).data);
}
Now, decltype(arg) actually varies from instantiation to instantiation, which is a good indicator that we're doing something sensible.
In addition of the forwarding problem denoted by Barry, there's a different reason why you cannot have constexpr on init. This is because you contain a reference to a temporary inside data_of_t.
You see, you are containing a type obtained from overload resolution from a forwarding reference:
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... _vs) {
return data_of_t<T, Ts...>(std::forward<Ts>(_vs)...);
};
The Ts... in this case could be something like int, float const&, double&. You send those reference type and then you contain them inside of the std::tuple in data_of_t.
Those temporaries are local variables from the test function:
template <typename T = int>
class test {
public:
static constexpr auto func(int p0=0, int p1=1, int p2=3) noexcept {
return data_of <test<T>>
(data_of<test<T>>(p0, p1));
}
};
The problem here is that p0, p1, p2 are all local variable. You send them in test_of_t which will contain references to them, and you return the object containing all those reference to the local variable. This is maybe the cause of the MSVC crash. Compiler are required to provide diagnostic for any undefined behaviour in constexpr context. This crash is 100% a compiler bug and you should report it.
So how do you fix that?
Simply don't contain references by changing data_of:
template<typename T, typename... Ts>
constexpr auto data_of(Ts&&... _vs) {
return data_of_t<T, std::decay_t<Ts>...>(std::forward<Ts>(_vs)...);
};
This will decay the type thus removing the references and decay any reference to C array to pointers.
Then, you have to change your constructor. You call std::forward in there but it's no forwarding occurring if you decay in the template arguments.
template<typename... Vs> requires((std::same_as<std::decay_t<Vs>, Ts>) && ...)
constexpr data_of_t(Vs... _vs)
: data(std::forward<Vs>(_vs)...)
{}
This will add proper forwarding and also constrain it properly so it always do as data_of intended.
Just doing those change will remove UB from the code, but also change it a bit. The type data_of_t will always contain values, and won't contain references. If you want to send a reference, you will need something like std::ref, just like std::bind have to use to defer parameters.
You will still need to use std::forward<decltype(arg)>(arg).data for proper forwarding as #Barry stated
see example below live : https://onlinegdb.com/Hkg6iQ3ZNI
#include <iostream>
#include <utility>
#include <type_traits>
class A
{
public:
A(int v=-10):v_(v){}
void print()
{
std::cout << "called A: " << v_ << std::endl;
}
private:
int v_;
};
void f(int v)
{
std::cout << "called f: " << v << std::endl;
}
template<typename T,typename ... Args>
void run(A&& a,
T&& t,
Args&& ... args)
{
a.print();
t(std::forward<Args>(args)...);
}
template<typename T,typename ... Args>
void run(T&& t,
Args&& ... args)
{
run(A(),
std::forward<T>(t),
std::forward<Args>(args)...);
}
int main()
{
int v_function=1;
int v_a = 2;
run(f,v_function);
return 0;
}
The code above compiles, runs and print (as expected):
called A: -10
called f: 1
but if the main function is modified to:
int main()
{
int v_function=1;
int v_a = 2;
run(f,v_function);
// !! added lines !!
A a(v_a);
run(a,f,v_function);
return 0;
}
then compilation fails with error:
main.cpp:30:6: error: no match for call to ‘(A) (void (&)(int), int&)’
t(std::forward(args)...);
~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
which seems to indicate that even when an instance of A is passed as first argument, the overload function
void(*)(T&&,Args&&...)
is called, and not
void(*)(A&&,T&&,Args&&...)
With
template<typename T,typename ... Args>
void run(A&& a,
T&& t,
Args&& ... args)
a is not a forwarding reference, but an rvalue reference. That means when you do run(a,f,v_function); that function will not be selected because a is an lvalue and those can't be bound to rvalue references. There are two quick ways to fix this. First, use std::move on a like
run(std::move(a),f,v_function);
but this isn't great. a isn't actually moved in the function so you are kind of violating the principle of least surprise.
The second option is to make A in the function a template type so it becomes a forwarding reference and then you can constrain it to be of type A like
template<typename A_, typename T,typename ... Args, std::enable_if_t<std::is_same_v<std::decay_t<A_>, A>, bool> = true>
void run(A_&& a,
T&& t,
Args&& ... args)
{
a.print();
t(std::forward<Args>(args)...);
}
Your code works, if you are calling run with an rvalue.
Playable example here.
As NathanOliver already sad: void run(A&& a, T&& t, Args&& ... args) expects an rvalue reference.
Basic idea of an rvalue reference: You are passing an rvalue to a function (e.g. a string literal). That value will be copied to the function. This work is unnecessary. Instead, you are just "moving" the reference to that value, so that it is "owned" by a different part of your program. Move constructors are a good starting point for understanding this problem.
Consider the following snippet:
#include <type_traits>
#include <string>
template<typename T>
std::string stringify (const T& value)
{ return "dummy"; }
template<typename T>
class foo_class
{
public:
template<typename Converter = std::string(&)(const T&),
class = typename std::enable_if<std::is_convertible<
typename std::result_of<Converter(const T&)>::type,
std::string>
::value>::type>
foo_class(const T &value, const Converter &converter = stringify<T>) {}
};
int main(int,const char*[])
{
int X = 7;
foo_class<int> x(X);
foo_class<int> y(X, stringify<int>);
}
The first constructor compiles just fine, however the second one fails with the following error under Clang 3.6.2:
candidate template ignored: substitution failure [with Converter = std::__cxx11::basic_string<char> (const int &)]: function cannot return function type 'std::__cxx11::basic_string<char> (const int &)'
foo_class(const T &value, const Converter &converter = stringify<T>) {}
I dug down and found one way to fix it, by changing const Converter & to just Converter in the parameter list, although this might not be the desired behavior in some cases.
The error is caused by something in the std::enable_if<..> clause. I can remove it and the code compiles (and runs) just fine.
I am primarily interested in the question "why" - why does it work in the first case (when the function is selected as a default parameter), but not in the second case, when it is selected explicitly.
As a secondary question, what would be considered the best way to deal with the issue? I mean, I have one workaround, but would prefer to stick to the "const reference by default" policy for arguments that are not functions.
That's not how you are supposed to use result_of.
It should always be used with references, where the kind of the reference designates the value category of the corresponding expression in the call whose result you want to know.
So if you are calling converter as a const lvalue, then you'd do result_of<const Converter &(const T&)>. A few more examples:
// forward the value category of the function object
template<class F>
auto f(F&& f) -> std::result_of_t<F&&()> { return std::forward<F>(f)(); }
// always call as an lvalue, accept both lvalue and rvalue function object
template<class F>
auto f(F&& f) -> std::result_of_t<F&()> { return f(); }
// Accept everything by value, but call with all lvalues
template<class F, class... Args>
auto f(F f, Args... args) -> std::result_of_t<F&(Args&...)> { return f(args...); }
// Accept everything by value, and move them all
template<class F, class... Args>
auto f(F f, Args... args) -> std::result_of_t<F&&(Args&&...)> { return std::move(f)(std::move(args)...); }
In your second snippet, you have
Converter = std::string(const T&) // without reference
2 possibles fixes/workarounds:
Use std::decay_t
template<typename T>
class foo_class
{
public:
template<typename Converter = std::string(&)(const T&),
std::enable_if_t<
std::is_convertible<
std::result_of_t<std::decay_t<Converter>(const T&)>,
std::string>::value>* = nullptr>
foo_class(const T &value, const Converter &converter = stringify<T>) {}
};
Demo
or use
foo_class<int> y(X, &stringfy<int>);
I have the following
#include <iostream>
#include <memory>
template<typename _type>
class handle
{
using ptr = std::shared_ptr<_type>;
using pptr = std::shared_ptr<ptr>;
public:
handle(handle<_type> const & other) :
mData(make_pptr(*(other.mData)))
{}
handle(_type && data) :
mData(make_pptr(std::move(data)))
{}
private:
pptr mData;
template<typename ..._args>
constexpr auto make_ptr(_args && ...args)
{
return std::make_shared<_type>(std::forward<_args>(args)...);
}
constexpr auto make_pptr(ptr const & pointer)
{
return std::make_shared<ptr>(pointer);
}
template<typename ..._args>
constexpr auto make_pptr(_args && ...args)
{
return std::make_shared<ptr>(make_ptr(std::forward<_args>(args)...));
}
};
int main()
{
handle<int> h = 5;
handle<int> h2(h);
}
Compiled with g++-4.9 --std=c++14 -O0 -o main main.cpp the code
handle<int> h2(h);
does not compile. The problem functions are all the overloads of
make_pptr
As I understand it, the template function will always be chosen, as the compiler tries to find the most specialized function call and the perfect forwarding creates exactly that.
I found the following two pages who seem to handle that problem with the type trait std::enable_if and std::is_same.
https://akrzemi1.wordpress.com/2013/10/10/too-perfect-forwarding/
http://www.codesynthesis.com/~boris/blog/2012/05/30/perfect-forwarding-and-overload-resolution/
The actual question is, how can I change this function, so that the non-template functions will be called if I pass the factory function an already existing pointer?
Is there a common way to do it?
As Jarod's answer explains, in the constructor
handle(handle<_type> const & other) :
mData(make_pptr(*(other.mData)))
{}
you call make_pptr with an argument of type shared_ptr<_type>&, which makes the perfect forwarding overload of make_pptr a better match than the one that takes a shared_ptr<_type> const&. You can cast the argument to const& as he shows, or you could add another overload of make_pptr that takes a non-const lvalue reference.
constexpr auto make_pptr(ptr & pointer)
{
return std::make_shared<ptr>(pointer);
}
Yet another option is to constrain the perfect forwarding overload so that it is viable only when the first argument of the parameter pack is not a shared_ptr<_type>.
Some helpers to evaluate whether the first type in the parameter pack is a shared_ptr<T>
namespace detail
{
template<typename... _args>
using zeroth_type = typename std::tuple_element<0, std::tuple<_args...>>::type;
template<typename T, bool eval_args, typename... _args>
struct is_shared_ptr
: std::false_type
{};
template<typename T, typename... _args>
struct is_shared_ptr<T, true, _args...>
: std::is_same<std::decay_t<zeroth_type<_args...>>,
std::shared_ptr<T>
>
{};
}
Then constrain the perfect forwarding make_pptr as follows
template<typename ..._args,
typename = std::enable_if_t<
not detail::is_shared_ptr<_type, sizeof...(_args), _args...>::value
>
>
constexpr auto make_pptr(_args && ...args)
{
return std::make_shared<ptr>(make_ptr(std::forward<_args>(args)...));
}
I also had to change your make_ptr overload because the way you have it defined in your example requires that _type be constructible from nullptr.
constexpr auto make_ptr()
{
return std::make_shared<_type>();
// no nullptr arg above, shared_ptr default ctor will initialize _type* to nullptr
}
Live demo
When calling
handle<int> h2(h);
You call
handle(handle<_type> const & other) : mData(make_pptr(*(other.mData)))
{}
and *other.mData is a std::shared_ptr<_type>& and so you call
template<typename ..._args>
constexpr auto make_pptr(_args && ...args)
which is an exact match.
You may force the const with
handle(handle<_type> const & other) :
mData(make_pptr(static_cast<const ptr&>(*(other.mData))))
{}
to solve your issue.
or add overload for simple reference.
The implementation of std::forward in VS2013 is
template<class _Ty> inline
_Ty&& forward(typename remove_reference<_Ty>::type& _Arg)
{ // forward an lvalue
return (static_cast<_Ty&&>(_Arg));
}
template<class _Ty> inline
_Ty&& forward(typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT
{ // forward anything
static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
return (static_cast<_Ty&&>(_Arg));
}
One version for lvalue reference, one version for rvalue reference. Why not just use a universal reference for both rvalue and lvalue reference:
template <typename T, typename U>
T&& Forward(U&& arg) {
return static_cast<T&&>(arg);
}
Your version is not standard-compliant, as std::forward is is required to not compile when called with on an rvalue if T is an l-value reference. From [forward]:
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept;
2 Returns: static_cast<T&&>(t).
3 if the second form is instantiated with an lvalue reference type, the program is ill-formed.
std::forward is defined in this way to ensure that (some) misuses of std::forward do not compile. See n2951 for more discussion (although even n2951 does not use this exact form).
I'm expanding a bit on the problem you've pointed out here.
Your version would introduce a reference-dangling case if you attempt to bind a newly created rvalue to a l-value reference.
As Mankarse linked, the n2951 paper cites this case and, by simplifying it a bit, you can summarize it with the following code
#include <iostream>
using namespace std;
template <typename T, typename U>
T&& Forward(U&& arg) {
return static_cast<T&&>(arg);
}
class Container
{
int data_;
public:
explicit Container(int data = 1) // Set the data variable
: data_(data) {}
~Container() {data_ = -1;} // When destructed, first set the data to -1
void test()
{
if (data_ <= 0)
std::cout << "OPS! A is destructed!\n";
else
std::cout << "A = " << data_ << '\n';
}
};
// This class has a reference to the data object
class Reference_To_Container_Wrapper
{
const Container& a_;
public:
explicit Reference_To_Container_Wrapper(const Container& a) : a_(a) {}
// (I) This line causes problems! This "Container" returned will be destroyed and cause troubles!
const Container get() const {return a_;} // Build a new Container out of the reference and return it
};
template <class T>
struct ReferenceContainer
{
T should_be_valid_lvalue_ref;
template <class U> // U = Reference_To_Container_Wrapper
ReferenceContainer(U&& u) :
// We store a l-value reference to a container, but the container is from line (I)
// and thus will soon get destroyed and we'll have a dangling reference
should_be_valid_lvalue_ref(Forward<T>(std::move(u).get())) {}
};
int main() {
Container a(42); // This lives happily with perfect valid data
ReferenceContainer<const Container&> rc( (Reference_To_Container_Wrapper(a)) ); // Parenthesis necessary otherwise most vexing parse will think this is a function pointer..
// rc now has a dangling reference
Container newContainer = rc.should_be_valid_lvalue_ref; // From reference to Container
newContainer.test();
return 0;
}
which outputs "OPS! A is destructed!"
if you just add a "&" in the line
const Container& get() const {return a_;}
the above works just fine.
http://ideone.com/SyUXss