How to Avoid Undefined Execution Order for the Constructors When Using Std::Make_Tuple

how to avoid undefined execution order for the constructors when using std::make_tuple

The trivial solution is not to use std::make_tuple(...) in the first place but to construct a std::tuple<...> directly: The order in which constructors for the members are called is well defined:

template <typename>
std::istream& dummy(std::istream& in) {
return in;
}
template <typename... T>
std::tuple<T...> parse(std::istream& in) {
return std::tuple<T...>(dummy<T>(in)...);
}

The function template dummy<T>() is only used to have something to expand on. The order is imposed by construction order of the elements in the std::tuple<T...>:

template <typename... T>
template <typename... U>
std::tuple<T...>::tuple(U...&& arg)
: members_(std::forward<U>(arg)...) { // NOTE: pseudo code - the real code is
} // somewhat more complex

Following the discussion below and Xeo's comment it seems that a better alternative is to use

template <typename... T>
std::tuple<T...> parse(std::istream& in) {
return std::tuple<T...>{ T(in)... };
}

The use of brace initialization works because the order of evaluation of the arguments in a brace initializer list is the order in which they appear. The semantics of T{...} are described in 12.6.1 [class.explicit.init] paragraph 2 stating that it follows the rules of list initialization semantics (note: this has nothing to do with std::initializer_list which only works with homogenous types). The ordering constraint is in 8.5.4 [dcl.init.list] paragraph 4.

Tuple isn't being constructed in order?

std::tuple construction order is currently unspecified.

A proposal for a concrete decision on its order has been submitted to the committee but until then the order should not be relied on.

How to guarantee order of argument evaluation when calling a function object?

What about a silly wrapper class like this:

struct OrderedCall
{
template <typename F, typename ...Args>
OrderedCall(F && f, Args &&... args)
{
std::forward<F>(f)(std::forward<Args>(args)...);
}
};

Usage:

void foo(int, char, bool);

OrderedCall{foo, 5, 'x', false};

If you want a return value, you could pass it in by reference (you'll need some trait to extract the return type), or store it in the object, to get an interface like:

auto x = OrderedCall{foo, 5, 'x', false}.get_result();

how to create (initialize) a std::tuple from an array when the constructors have the same argument type (a file path)

Using <redi/index_tuple.h> to provide a compile-time integer sequence that produces the array indices in a pack expansion:

#include <tuple>
#include <assert.h>
#include <redi/index_tuple.h>

template<typename... T, unsigned... I>
inline std::tuple<T...>
myCreateTuple_impl(char** argv, redi::index_tuple<I...>)
{
return std::tuple<T...>(argv[I+1]...);
}

template<typename... T>
inline std::tuple<T...>
myCreateTuple(int argc, char** argv)
{
assert(argc > sizeof...(T));
return myCreateTuple_impl<T...>(argv, redi::to_index_tuple<T...>());
}

c++ : creating tuple & adding an element

The error that you see implies that one of the members that the tuple contains is not default-constructible. In such case you must initialise the member tuple with appropriate arguments to use the non-default constructors of the members of the tuple.

Also I would like to know if there any tuple method for adding a thing.

Nope. A tuple contains all of its members from the beginning of its lifetime until the end. There is no way to add or remove elements.

how to create (initialize) a std::tuple from an array when the constructors have the same argument type (a file path)

Using <redi/index_tuple.h> to provide a compile-time integer sequence that produces the array indices in a pack expansion:

#include <tuple>
#include <assert.h>
#include <redi/index_tuple.h>

template<typename... T, unsigned... I>
inline std::tuple<T...>
myCreateTuple_impl(char** argv, redi::index_tuple<I...>)
{
return std::tuple<T...>(argv[I+1]...);
}

template<typename... T>
inline std::tuple<T...>
myCreateTuple(int argc, char** argv)
{
assert(argc > sizeof...(T));
return myCreateTuple_impl<T...>(argv, redi::to_index_tuple<T...>());
}

How to create a new variable and use it in std::tie at the same time?

@MikaelPersson had the right link. Basically, there's no great way to do this. Though, there are some clever ways based on N3802. Namely, use

// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}

Then, write

// With compose
{
auto foo = apply([&bar](auto && foo,auto && bar_) {
bar=std::move(bar_);
return std::move(foo);
}, example());
}

And, yes, this whole thing is ugly, but the situation did come up in some instance I had. Nevertheless, as @MikaelPersson's link shows, this is a general issue and not one fully resolved yet.

Is there a way to expand and call a tuple of std::functions?

You have to remove the {} in your funcs assignment otherwise this will create an initializer_list and not a tuple

static inline auto funcs = std::make_tuple(std::function<Ts()>()...);

Then you can call each tuple function with help of std::apply and a generic variadic lambda:

auto call = [](auto&&...funcs) {
(funcs(),...);
};

int main()
{
A<int, float, char> l;
std::get<0>(l.funcs) = []() { cout << "Calling int()" << endl; return 1; };
std::get<1>(l.funcs) = []() { cout << "Calling float()" << endl; return 1.f; };
std::get<2>(l.funcs) = []() { cout << "Calling char()" << endl; return '1'; };

std::apply(call, l.funcs);
return 0;
}

See the live example: https://onlinegdb.com/HJfQ95TpS

C++ multiple forwarding of one reference: first copy and then move

I'm not sure the order-of-operations on the object initializations matter. Due to the perfect forwarding, no copies or moves are actually made (i.e. only lvalue references and rvalue references are passed around) until the std::tuple constructor is called. And, at that point, it depends on the implementation details of std::tuple.

Consider if instead of std::tuple you used the following my_tup struct:

template<typename T1, typename T2>
struct my_tup
{
template <typename A, typename B>
my_tup(A&& a, B&& b)
: t1(std::forward<A>(a)), t2(std::forward<B>(b))
{
}

T1 t1;
T2 t2;
};

This prints, as expected, "copy" and then "move" (coliru). But if instead you have:

template<typename T1, typename T2>
struct my_tup
{
template <typename A, typename B>
my_tup(A&& a, B&& b)
: t2(std::forward<B>(b)), t1(std::forward<A>(a))
{
}

T2 t2;
T1 t1;
};

Then this prints "move", then "copy", as std::tuple does (coliru).

Likely due to the way the variadic template is expanded, std::tuple must be dealing with the arguments in a right-to-left manner. I am not sure whether this is implementation-dependent or specified in the spec.



Related Topics



Leave a reply



Submit