So I have a struct X:
struct X
{
int typecode;
char* pData;
int length;
...
}
and a long list of types, we'll call this set TS. TS includes most primitive types and several class types.
For each type T in TS I have a regular function defined:
void setup(X& x, const T& t);
For example for T = string setup looks like:
void setup(X& x, const string& s)
{
x.typecode = X_STRING;
x.pData = s.c_str();
x.length = s.size();
...
}
Now I have a template function convert_to_x:
template<class T>
X convert_to_x(const T& t)
{
X x;
memset(x, 0, sizeof(x));
setup(x, t);
return x;
}
And a function f that takes an array of X:
void f(X* xs, int num_args);
And further a variadic template function g:
template<class... Args)
void g(Args... args)
{
constexpr num_args = sizeof...(args);
X xs[] = { convert_to_x(args)... };
f(xs, num_args);
}
What is going on is that you can call g with any number of parameters and types and it will convert the parameters to an array of X type, and then call f.
The problem is that if g is called with a type that is not in TS, but is convertible to a type in TS, the following happens:
A converting constructor is called to create a temporary t.
setup will store a pointer to this temporary.
the temporary is destroyed.
f is called with an xs that contains hanging pointers.
I need a way at entry to g to convert any arguments that are convertible to a type in TS but are not of a type in TS, and keep them around for the whole scope of g.
What is the best way to achieve this?
Update:
One way I just thought of that may work is to define a regular function convert for each type T in TS as follows:
T convert(const T& t) { return t; }
and then define a wrapper for g:
template<class... Args>
void g2(Args... args)
{
g(convert(args)...);
}
but I think this will cause unnecessary copying of types that are already in TS and don't need converting. Is there some way to use rvalue/lvalue semantics to avoid this?
Update 2:
Maybe this would work:
For each T in TS:
const T& convert(const T& t) { return t; }
T convert(const T&& t) { return t; }
then:
template<class... Args>
void g2(Args... args)
{
g(convert(args)...);
}
Are there any cases where setup could possibly receive a temporary with the above?
You can add deleted overloads for setup:
void setup(X& x, const string& s) = delete;
Since rvalue references bind to temporaries more readily than const lvalue references, calling setup with a temporary will select the overload with rvalue references. But since this setup overload is deleted, it would be illegal to actually call it. So when g is called with the wrong type of argument, the line X xs[] = { convert_to_x(args)... }; requires convert_to_args to call a deleted version of setup, so that instantiation fails. In turn, it causes the particular instantiation of g to fail as well.
Edit
Looking at your update #2, this should work. Since convert already causes the best conversion to one of the TS, g should never be called with unwanted types. So no temporaries would be created by unwanted conversions upon the invocation of setup. Thus, any temporaries would be arguments to g, and would be guaranteed to live for the duration of g.
Edit 2
You're right that T convert(const T&& t) { return t; } could result in values being unnecessarily copied. But this is easily fixed:
const T& convert(const T& t) { return t; }
const T&& convert(const T&& t) { return std::move(t); }
you won't get any copying of values. Lifetime of temporaries is correct, too, since temporaries live until the end of the full expression in which they are created:
template<class... Args>
void g2(Args... args)
{
g(convert(args)...);
} // ^^^^ temporaries created by a conversion here will live until g returns
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
Consider this minimal example
template <class T>
class Foo
{
public:
Foo(const T& t_)
: t(t_)
{
}
Foo(T&& t_)
: t(std::move(t_))
{
}
T t;
};
template <typename F>
Foo<F> makeFoo(F&& f)
{
return Foo<F>(std::forward<F>(f));
}
int main()
{
class C
{
};
C c;
makeFoo(c);
}
MSVC 2017 fails with a redefinition error of Foo's ctor. Apparently T gets deduced to C& instead of the intended C. How exactly does that happen and how to modify the code so that it does what is inteded: either copy construct Foo::t from a const reference or move construct it from an r-value.
In C++17 you can simply write:
template <typename F>
auto makeFoo(F&& f)
{
return Foo(std::forward<F>(f));
}
because of class template argument deduction.
In C++14 you can write:
template <typename F>
auto makeFoo(F&& f)
{
return Foo<std::decay_t<F>>(std::forward<F>(f));
}
template <class F, class R = std::decay_t<F>>
Foo<R> makeFoo(F&& f)
{
return Foo<R>(std::forward<F>(f));
}
that is a clean and simple way to solve your problem.
Decay is an appropriate way to convert a type into a type suitable for storing somewhere. It does bad things with array types but otherwise does pretty much the right thing; your code doesn't work with array types anyhow.
The compiler error is due to reference collapsing rules.
X X& X const& X&&
int int& int const& int&&
int& int& int& int&
int const int const& int const& int const&&
int&& int& int& int&&
int const& int const& int const& int const&
these may seem strange.
The first rule is that a const reference is a reference, but a reference to const is different. You cannot qualify the "reference" part; you can only const-qualify the referred part.
When you have T=int&, when you calculate T const or const T, you just get int&.
The second part has to do with how using r and l value references together work. When you do int& && or int&& & (which you cannot do directly; instead you do T=int& then T&& or T=int&& and T&), you always get an lvalue reference -- T&. lvalue wins out over rvalue.
Then we add in the rules for how T&& types are deduced; if you pass a mutable lvalue of type C, you get T=C& in the call to makeFoo.
So you had:
template<F = C&>
Foo<C&> makeFoo( C& && f )
as your signature, aka
template<F = C&>
Foo<C&> makeFoo( C& f )
now we examine Foo<C&>. It has two ctors:
Foo( C& const& )
Foo( C& && )
for the first one, const on a reference is discarded:
Foo( C& & )
Foo( C& && )
next, a reference to a reference is a reference, and lvalue references win out over rvalue references:
Foo( C& )
Foo( C& )
and there we go, two identical signature constructors.
TL;DR -- do the thing at the start of this answer.
Issue is that typename provided to class is reference in one case:
template <typename F>
Foo<F> makeFoo(F&& f)
{
return Foo<F>(std::forward<F>(f));
}
becomes
template <>
Foo<C&> makeFoo(C& f)
{
return Foo<C&>(std::forward<C&>(f));
}
You probably want some decay:
template <typename F>
Foo<std::decay_t<F>> makeFoo(F&& f)
{
return Foo<std::decay_t<F>>(std::forward<F>(f));
}
This happens because of reference collapsing.
The F&& in your code is a forwarding reference, which means it can be either an lvalue reference or an rvalue reference depending on the type of the argument to which it binds.
In your case, if F&& binds to an argument of type C&& (an rvalue reference to C), F is simply deduced as C. However, if F&& binds to an argument of type C& (as in your example), the reference collapsing rules determine the type deduced for F:
T& & -> T&
T& && -> T&
T&& & -> T&
T&& && -> T&&
Thus, F is deduced as C&, since C& && collapses to C&.
You can use remove_reference to remove any reference from the deduced type:
remove_reference_t<C> -> C
remove_reference_t<C&> -> C
You will probably also want to use remove_cv to remove any potential const (or volatile) qualifier:
remove_cv_t<remove_reference_t<C>> -> C
remove_cv_t<remove_reference_t<C&>> -> C
remove_cv_t<remove_reference_t<C const>> -> C
remove_cv_t<remove_reference_t<C const&>> -> C
In C++20, there is a combined remove_cvref trait which can save some typing. However, many implementations just use decay, which does the same thing, but also turns array and function types into pointers, which may or may not be desirable depending on your use case (some parts of the standard library have switched from using decay to using remove_cvref in C++20).
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>);
On a blog on the progress of C++17 I read the following:
P0007 proposes a helper function template as_const, which simply
takes a reference and returns it as a reference to const.
template <typename T> std::add_const_t<T>& as_const(T& t) { return t }
template <typename T> void as_const(T const&&) = delete;
Why is the const&& overload deleted?
Consider what would happen if you didn't have that overload, and try to pass in a const rvalue:
template <typename T> const T &as_const(T &t) { return t; }
struct S { };
const S f() { return S{}; }
int main() {
// auto & ref = as_const(S()); // correctly detected as invalid already
auto & ref = as_const(f()); // accepted
}
This would be accepted because T would be deduced as const S, and temporaries can bind to const S &. The result would be that you accidentally get an lvalue reference to a temporary, which will be destroyed right after ref has been initialised. Almost all users taking lvalues (whether variables or function parameters) don't expect to be passed temporaries; silently accepting temporaries means that you easily silently get dangling references.
I’m looking for a workaround for bit-field in overload resolution for template.
I have a function that I templated for perfect forwarding of its arguments:
template <typename... Args> void f(Args &&...args) { }
If I try to use it with a bit-field argument, like this:
struct bits { unsigned int foo:1; };
bits b{1};
f(b.foo);
…it fails to compile:
main.cpp:26:7: error: non-const reference cannot bind to bit-field 'foo'
f(b.foo);
^~~~~
Is there a way to overload f() such that it takes bit-fields by value but still takes other arguments by reference in the common case?
So far I haven't been able to. For instance, if I add an overload that takes arguments by value…
main.cpp:27:5: error: call to 'f' is ambiguous
f(b.foo);
^
http://coliru.stacked-crooked.com/view?id=b694c6cc3a52e0c14bedd6a26790d99d-e54ee7a04e4b807da0930236d4cc94dc
It can be done, if poorly. I recommend not doing this. Basically, the key part is since you can't have a pointer or a reference to a bitfield, you instead use a lambda which sets the bitfield for you.
I dislike macros as much as the next guy, but it's the only way I could think of to avoid requiring callers to put in a lambda at the callsite.
template<class assigner_type>
struct bitfieldref_type {
bitfieldref_type(bool value, assigner_type&& assign) :value(value), assign(std::move(assign)) {}
operator bool() const {return value;}
bitfieldref_type& operator=(bool v) {assign(v); value=v; return *this;}
private:
bool value;
assigner_type assign;
};
template<class assigner_type>
bitfieldref_type<assigner_type> make_bitfieldref(bool value, assigner_type&& assign)
{return {value, std::move(assign)};}
//macro is optional
#define bitfieldref(X) make_bitfieldref(X, [&](bool v)->void{X=v;})
usage:
template <class T, typename... Args> void proof_it_works(T&& first)
{first = 0;}
template <class T, typename... Args> void proof_it_works(T&& first, Args &&...args) {
first = 0;
proof_it_works(std::forward<Args>(args)...);
}
template <typename... Args> void f(Args &&...args) {proof_it_works(std::forward<Args>(args)...);}
int main() {
struct bits { unsigned int foo:1; };
bits b{1};
int a = -1;
float c = 3.14;
f(a, bitfieldref(b.foo), c);
std::cout << a << b.foo << c;
return 0;
}
I just noticed that my bitfieldref_type assumes the value is a bool, instead of a unsigned int, but I'll leave fixing that as an excersize for the user.
It cannot be done (at least not how you tried it) because the Standard says so (bold emphasis mine):
13.3.3.1.4 Reference binding [over.ics.ref]
4 Other restrictions on binding a reference to a particular argument
that are not based on the types of the reference and the argument do
not affect the formation of a standard conversion sequence, however.
[Example: a function with an “lvalue reference to int” parameter can
be a viable candidate even if the corresponding argument is an int
bit-field. The formation of implicit conversion sequences treats the
int bit-field as an int lvalue and finds an exact match with the
parameter. If the function is selected by overload resolution, the
call will nonetheless be ill-formed because of the prohibition on
binding a non-const lvalue reference to a bit-field (8.5.3). — end
example ]
This explains why
the original example fails to compile, because the reference cannot bind to a bit-field
adding an overload template<typename... Arg> f(Args.. args) gave you the ambiguity: overload resoution ended in a tie, and the reference-binding-to-bitfield prohibition never came into play.
This is the best answer I can come up with:
template <typename... Args> void f(Args &&...args) { }
struct bits { unsigned int foo:1; };
template <typename T> const T constipate(T v)
{ return(static_cast<const T>(v)); }
void bar()
{
bits b{1};
f(constipate(b.foo));
}
EDIT: There's an easier solution, that eliminates the need for the 'constipate' template:
void bar()
{
bits b{1};
f(b.foo + 0);
}