I am wondering how std::visit return type conversions are supposed to work.
The context is the following:
I have a variant object and I want to apply (through std::visit) different functions depending on its underlying type. The result of each function may have a different type, but then I would like std::visit to pack it up in a variant type.
Pseudo-code:
I have:
variant<A,B> obj
f(A) -> A
f(B) -> B
I want:
if obj is of type A => apply f(A) => resA of type A => pack it in variant<A,B>
if obj is of type B => apply f(B) => resB of type B => pack it in variant<A,B>
Now, according to cppreference, the return type of std::visit is "The value returned by the selected invocation of the visitor, converted to the common type of all possible std::invoke expressions"
But what common type means is not specified. Is it std::common_type ? In this case, it doesn't work with gcc 7.2:
#include <variant>
#include <iostream>
#include <type_traits>
struct A {
int i;
};
struct B {
int j;
};
// the standard allows to specialize std::common_type
namespace std {
template<>
struct common_type<A,B> {
using type = std::variant<A,B>;
};
template<>
struct common_type<B,A> {
using type = std::variant<A,B>;
};
}
struct Functor {
auto
operator()(A a) -> A {
return {2*a.i};
}
auto
operator()(B b) -> B {
return {3*b.j};
}
};
int main() {
std::variant<A,B> var = A{42};
auto res = std::visit( Functor() , var ); // error: invalid conversion from 'std::__success_type<B>::type (*)(Functor&&, std::variant<A, B>&) {aka B (*)(Functor&&, std::variant<A, B>&)}' to 'A (*)(Functor&&, std::variant<A, B>&)' [-fpermissive]
}
What should I do to express this unpack - apply visitation - repack pattern?
Notes:
1) Specializing std::common_type<A(*)(Ts...),B(*)(Ts...)> won't cut it. This would do the trick but rely on a particular std::lib implementation detail.
Plus it doesn't work for multi-visitation.
2) The example I have given is really reduced to the bare minimum, but you have to imagine that the visitation mechanism I want to provide is on the library side, and the visitors are on the client side and can be arbitrary complicated: unknown number and types of arguments, unknown return types. The library should just provide visitation and a pre-defined set of std::common_type specializations to be used for visitation return types. So for instance, defining
auto f = [](auto x) -> variant<A,B> { return Functor()(x); };
and then applying std::visit to f is not a viable option: from the library side, I can't predefine this kind of lambda without knowing the "packed" return type. [The main problem is that I see no way of asking the language for the std::common_type of a particular overload set]
You can create your own visit layer, something like:
template <typename Visitor, typename ... Ts>
decltype(auto) my_visit(Visitor&& vis, const std::variant<Ts...>& var)
{
return std::visit([&](auto&& e)
-> std::common_type_t<decltype(vis(std::declval<Ts>()))...>
{
return vis(e);
}, var);
}
Demo
template<class...>struct types{using type=types;};
template<class F, class Types>
struct results;
template<class F, class...Ts>
struct results<F, types<Ts...>>:
types<std::invoke_result_t<F,Ts>...>
{};
this gives you a the result of applying F to a bundle of types as a bundle of types.
Add transcribe to-from variant, maybe duplicate removal, a wrapper that takes F and a variant<Ts...> and creates an F2 that calls F and returns said variant, then passes F2 to visit, and we are hakf way there.
The other half is to handle multiple variants. To get that, we need to take cross product of multiple type bundles, get the invoke result of all of them, and bundle that up.
Your main problem is the fact that std::visit expressly requires all return types of the various invocations provided by the Visitor to be of the same type, and specializing std::common_type does nothing to fix that. The "Common Type" descriptor you pulled from the Standard is meant colloquially, not as a literal type.
In other words, the Visitor must take the form of
struct Visitor {
using some_type = /*...*/;
some_type operator()(A const& a);
some_type operator()(B const& b);
};
Fortunately, this is a problem that solves itself. Because there already is a common type that can be assigned from this sort of permutation on the stored value: the variant you described in the first place.
struct Functor {
std::variant<A,B> operator()(A const& a) const {
return A{2*a.i};
}
std::variant<A,B> operator()(B const& b) const {
return B{3*b.j};
}
};
This should compile and yield the behavior you're expecting.
My solution for multiple visitation. Thanks to Jarod42 for showing me the way with single variant visitation.
Live Demo
The main problem is to generate the cross-product of all possible calls to an overload set.
This answer does not address the problem of a generic conversion of return types, I just did an ad-hoc specialization of std::common_type (I think this is enougth to suit my needs, but feel free to contribute!).
See compile-time tests at the end to understand each template meta-function.
Feel free to suggest simplifications (std::index_sequence anyone?)
#include <variant>
#include <iostream>
#include <type_traits>
// ========= Library code ========= //
// --- Operations on types --- //
template<class... Ts>
struct Types; // used to "box" types together
// Lisp-like terminology
template<class Head, class Tail>
struct Cons_types;
template<class Head, class... Ts>
struct Cons_types<Head,Types<Ts...>> {
using type = Types<Head,Ts...>;
};
template<class... _Types>
struct Cat_types;
template<class _Types, class... Other_types>
struct Cat_types<_Types,Other_types...> {
using type = typename Cat_types<_Types, typename Cat_types<Other_types...>::type>::type;
};
template<class... T0s, class... T1s>
struct Cat_types< Types<T0s...> , Types<T1s...> > {
using type = Types< T0s..., T1s... >;
};
template<class... T0s>
struct Cat_types< Types<T0s...> > {
using type = Types< T0s... >;
};
template<class Head, class Types_of_types>
struct Cons_each_types;
template<class Head, class... Ts>
struct Cons_each_types<Head,Types<Ts...>> {
using type = Types< typename Cons_types<Head,Ts>::type... >;
};
template<class Head>
struct Cons_each_types<Head,Types<>> {
using type = Types< Types<Head> >;
};
template<class _Types>
struct Cross_product;
template<class... Ts, class... Other_types>
struct Cross_product< Types< Types<Ts...>, Other_types... > > {
using type = typename Cat_types< typename Cons_each_types<Ts,typename Cross_product<Types<Other_types...>>::type>::type...>::type;
};
template<>
struct Cross_product<Types<>> {
using type = Types<>;
};
// --- Operations on return types --- //
template<class Func, class _Types>
struct Common_return_type;
template<class Func, class... Args0, class... Other_types>
struct Common_return_type<Func, Types< Types<Args0...>, Other_types... >> {
using type =
std::common_type_t<
std::result_of_t<Func(Args0...)>, // C++14, to be replaced by std::invoke_result_t in C++17
typename Common_return_type<Func,Types<Other_types...>>::type
>;
};
template<class Func, class... Args0>
struct Common_return_type<Func, Types< Types<Args0...> >> {
using type = std::result_of_t<Func(Args0...)>;
};
// --- Operations on variants --- //
template<class... Vars>
struct Vars_to_types;
template<class... Ts, class... Vars>
struct Vars_to_types<std::variant<Ts...>,Vars...> {
using type = typename Cons_types< Types<Ts...> , typename Vars_to_types<Vars...>::type >::type;
};
template<>
struct Vars_to_types<> {
using type = Types<>;
};
template<class Func, class... Vars>
// requires Func is callable
// requires Args are std::variants
struct Common_return_type_of_variant_args {
using Variant_args_types = typename Vars_to_types<Vars...>::type;
using All_args_possibilities = typename Cross_product<Variant_args_types>::type;
using type = typename Common_return_type<Func,All_args_possibilities>::type;
};
template <typename Func, class... Args>
// requires Args are std::variants
decltype(auto)
visit_ext(Func&& f, Args... args) {
using Res_type = typename Common_return_type_of_variant_args<Func,Args...>::type;
return std::visit(
[&](auto&&... e)
-> Res_type
{
return f(std::forward<decltype(e)>(e)...);
},
std::forward<Args>(args)...);
}
// ========= Application code ========= //
struct A {
int i;
};
struct B {
int j;
};
// This part is not generic but is enough
namespace std {
template<>
struct common_type<A,B> {
using type = std::variant<A,B>;
};
template<>
struct common_type<B,A> {
using type = std::variant<A,B>;
};
template<>
struct common_type<A,std::variant<A,B>> {
using type = std::variant<A,B>;
};
template<>
struct common_type<std::variant<A,B>,A> {
using type = std::variant<A,B>;
};
template<>
struct common_type<B,std::variant<A,B>> {
using type = std::variant<A,B>;
};
template<>
struct common_type<std::variant<A,B>,B> {
using type = std::variant<A,B>;
};
}
struct Functor {
auto
operator()(A a0,A a1) -> A {
return {a0.i+2*a1.i};
}
auto
operator()(A a0,B b1) -> A {
return {3*a0.i+4*b1.j};
}
auto
operator()(B b0,A a1) -> B {
return {5*b0.j+6*a1.i};
}
auto
operator()(B b0,B b1) -> B {
return {7*b0.j+8*b1.j};
}
};
// ========= Tests and final visit call ========= //
int main() {
std::variant<A,B> var0;
std::variant<A,B> var1;
using Variant_args_types = typename Vars_to_types<decltype(var0),decltype(var1)>::type;
static_assert(
std::is_same_v<
Types< Types<A,B>, Types<A,B> >,
Variant_args_types
>
);
using Cons_A_Nothing = typename Cons_each_types<A, Types<> >::type;
static_assert(
std::is_same_v<
Types< Types<A> >,
Cons_A_Nothing
>
);
using Cons_A_AB = typename Cons_each_types<A, Types<Types<A>,Types<B>> >::type;
using Cons_B_AB = typename Cons_each_types<B, Types<Types<A>,Types<B>> >::type;
static_assert(
std::is_same_v<
Types< Types<A,A>, Types<A,B> >,
Cons_A_AB
>
);
using Cat_types_A = typename Cat_types<Cons_A_Nothing>::type;
static_assert(
std::is_same_v<
Types< Types<A> >,
Cat_types_A
>
);
using Cat_types_AA_AB_BA_BB = typename Cat_types<Cons_A_AB,Cons_B_AB>::type;
static_assert(
std::is_same_v<
Types< Types<A,A>, Types<A,B>, Types<B,A>, Types<B,B> >,
Cat_types_AA_AB_BA_BB
>
);
using Depth_x1_1_cross_product = typename Cross_product<Types<Types<A>>>::type;
static_assert(
std::is_same_v<
Types< Types<A> >,
Depth_x1_1_cross_product
>
);
using Depth_x2_1_1_cross_product = typename Cross_product<Types<Types<A>,Types<B>>>::type;
static_assert(
std::is_same_v<
Types< Types<A,B> >,
Depth_x2_1_1_cross_product
>
);
using All_args_possibilities = typename Cross_product<Variant_args_types>::type;
static_assert(
std::is_same_v<
Types< Types<A,A>, Types<A,B>, Types<B,A>, Types<B,B> >,
All_args_possibilities
>
);
using Functor_AorB_AorB_common_return_type = typename Common_return_type<Functor,All_args_possibilities>::type;
static_assert(
std::is_same_v<
std::variant<A,B>,
Functor_AorB_AorB_common_return_type
>
);
using Functor_varAB_varAB_common_return_type = typename Common_return_type_of_variant_args<Functor,decltype(var0),decltype(var1)>::type;
static_assert(
std::is_same_v<
std::variant<A,B>,
Functor_varAB_varAB_common_return_type
>
);
var0 = A{42};
var1 = A{43};
auto res0 = visit_ext(Functor(), var0,var1);
std::cout << "res0 = " << std::get<A>(res0).i << "\n";
var0 = A{42};
var1 = B{43};
auto res1 = visit_ext(Functor(), var0,var1);
std::cout << "res1 = " << std::get<A>(res1).i << "\n";
var0 = B{42};
var1 = A{43};
auto res2 = visit_ext(Functor(), var0,var1);
std::cout << "res2 = " << std::get<B>(res2).j << "\n";
var0 = B{42};
var1 = B{43};
auto res3 = visit_ext(Functor(), var0,var1);
std::cout << "res3 = " << std::get<B>(res3).j << "\n";
}
Related
This is a followup on this answer.
Assume we have two types of std:variant with partly the same member types. For instance if we have
struct Monday {};
struct Tuesday {};
/* ... etc. */
using WeekDay= std::variant<Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday>;
using Working_Day= std::variant<Monday, Tuesday, Wednesday, Thursday, Friday>;
Working_Day is a sub-type of WeekDay. Now how can we copy a variable of one type to a variable of the other type? If all type members of the source are type members of the target a conversion function can be defined as
template <typename To, typename From>
To var2var( From && from )
{
return std::visit(
[]( auto && elem ) { return To( std::forward<decltype(elem)>( elem ) ); },
std::forward<From>( from ) );
}
It can be used as
Working_Day d1= Tuesday{};
WeekDay d2= var2var<WeekDay>( d1 );
Trying this the other way around, i.e. casting a WeekDay into a Working_Day, results in a compile time error. Is there any solution for this?
Apparently the requirement is that if the type isn't present in the target variant, throw an exception. We can do that by introducing a new type which is only exactly convertible to a specific target:
template <typename T>
struct Exactly {
template <typename U, std::enable_if_t<std::is_same_v<T, U>, int> = 0>
operator U() const;
};
And then use that to either construct or throw:
template <typename To, typename From>
To unsafe_variant_cast(From && from)
{
return std::visit([](auto&& elem) -> To {
using U = std::decay_t<decltype(elem)>;
if constexpr (std::is_constructible_v<To, Exactly<U>>) {
return To(std::forward<decltype(elem)>(elem));
} else {
throw std::runtime_error("Bad type");
}
}, std::forward<From>(from));
}
Note that you need to explicitly provide a return type because otherwise in the exceptional case, it would get deduced to void and the visitors wouldn't all have the same return type.
The use of Exactly<U> as opposed to just decltype(elem) means that casting a variant<int> to a variant<unsigned int> will throw instead of succeeding. If the intend is to have it succeed, you can use decltype(elem) instead.
An alternative here would be to use Boost.Mp11, in which everything template metaprogramming related is a one-liner. This is also a more direct check:
template <typename To, typename From>
To unsafe_variant_cast(From && from)
{
return std::visit([](auto&& elem) -> To {
using U = std::decay_t<decltype(elem)>;
if constexpr (mp_contains<To, U>::value) {
return To(std::forward<decltype(elem)>(elem));
} else {
throw std::runtime_error("Bad type");
}
}, std::forward<From>(from));
}
The reason why the example above does not work is that std::visit requires operator() of the submitted functional object to be overloaded for each type member of the source variant. But for some of these types there is no matching constructor of the target variant.
The solution is to treat visiting differently for types which both variants have in common and those which are members of the source variant only.
template <class To, class From>
To var2var( From && from )
{
using FRM= std::remove_reference_t<From>;
using TO= std::remove_reference_t<To>;
using common_types= typename split_types<TO, FRM>::common_types;
using single_types= typename split_types<TO, FRM>::single_types;
return std::visit(
conversion_visitor<TO, common_types, single_types>(),
std::forward<From>( from ) );
}
Here std::visit gets an object of struct conversion_visitor. The latter takes template parameters common_types and single_types, which contain the type members of the source variant split in the mentioned way.
template<class... T> struct type_list {};
template <class To, class V1, class V2>
struct conversion_visitor;
template <class To, class... CT, class... ST>
struct conversion_visitor< To, type_list<CT...>, type_list<ST...> >
: public gen_variant<To, CT>...
, public not_gen_variant<To, ST>...
{
using gen_variant<To,CT>::operator()...;
using not_gen_variant<To,ST>::operator()...;
};
type_list is a container for types, which we use here because a variant cannot be empty. conversion_visitor is derived from structs gen_variant and not_gen_variant which both overload operator().
template<class To, class T>
struct gen_variant
{
To operator()( T const & elem ) { return To( elem ); }
To operator()( T && elem ) { return To( std::forward<T>( elem ) ); }
};
template<class To, class T>
struct not_gen_variant
{
To operator()( T const & ) { throw std::runtime_error("Type of element in source variant is no type member of target variant"); }
};
not_gen_variant is meant to treat the error cases, i.e. the cases in which the source contains a variable of a type which is not a member of the target variant. It throws in this example. Alternatively it could return a std::monostate if that is contained in the target variant.
With these definitions std::visit will call conversion_visitor::operator(). If the variable stored in the source has a type which the target can handle, that call is forwarded to gen_variant::operator(). Otherwise it is forwarded to not_gen_variant::operator(). gen_variant::operator() just calls the constructor of the target variant with the source element as argument.
What is left is to describe how to obtain common_types and single_types using struct split_types.
template<class T1, class T2>
struct split_types;
template<class... To, class... From>
struct split_types< std::variant<To...>, std::variant<From...> >
{
using to_tl= type_list<std::remove_reference_t<To>...>;
using from_tl= type_list<std::remove_reference_t<From>...>;
using common_types= typename split_types_h<to_tl, from_tl, type_list<>, type_list<> >::common_types;
using single_types= typename split_types_h<to_tl, from_tl, type_list<>, type_list<> >::single_types;
};
split_types takes the target and the source variant as template parameters. It first puts the members of those variants into type_lists to_tl and from_tl. These are forwarded to a helper split_types_h. Here the two empty type_lists will be filled up with the common and the single types as follows.
template<class T1, class T2, bool>
struct append_if;
template<class... Ts, class T>
struct append_if< type_list<Ts...>, T, true >
{
using type= type_list< Ts..., T >;
};
template<class... Ts, class T>
struct append_if< type_list<Ts...>, T, false >
{
using type= type_list< Ts... >;
};
template<class T1, class T2, bool b>
using append_if_t= typename append_if<T1, T2, b>::type;
template<class T1, class T2, class CT, class ST >
struct split_types_h;
template<class... T1, class... CT, class... ST>
struct split_types_h< type_list<T1...>, type_list<>, type_list<CT...>, type_list<ST...> >
{
using common_types= type_list<CT...>;
using single_types= type_list<ST...>;
};
template<class... T1, class T2f, class... T2, class... CT, class... ST>
struct split_types_h< type_list<T1...>, type_list<T2f,T2...>, type_list<CT...>, type_list<ST...> >
{
enum : bool { contains= (std::is_same_v<T2f,T1> || ...) };
using c_types_h= append_if_t<type_list<CT...>, T2f, contains>;
using s_types_h= append_if_t<type_list<ST...>, T2f, !contains>;
using common_types= typename split_types_h<type_list<T1...>, type_list<T2...>, c_types_h, s_types_h>::common_types;
using single_types= typename split_types_h<type_list<T1...>, type_list<T2...>, c_types_h, s_types_h>::single_types;
};
split_types_h takes one type member of the source (type_list<T2f,T2...>) after the other and checks if the target also contains it. If so the type (T2f) is appended to common_types (with the help of c_types_h). Otherwise it is appended to single_types.
The casting function can be used as follows (live demo).
Working_Day d1= Tuesday{};
Working_Day d2= d1;
WeekDay d3= Saturday{};
d3= var2var<WeekDay>( d1 );
d2= var2var<Working_Day>( d3 );
d2= var2var<Working_Day>( d1 );
try
{
WeekDay d4= Sunday{};
d1= var2var<Working_Day>( d4 );
}
catch( std::runtime_error & err )
{
std::cerr << "Runtime error caught: " << err.what() << '\n';
}
Your problem is that not all types in the source variant are handled by the destination.
We can fix this.
template<class...Fs>
struct overloaded : Fs... {
using Fs::operator()...;
};
template<class...Fs>
overloaded(Fs&&...)->overloaded<std::decay_t<Fs>...>;
this is a helper that lets us pass around lambda or function overloads.
template<class To, class From>
To var2var( From && from )
{
return std::visit(
overloaded{
[]( To elem ) { return elem; },
[]( auto&& x )
->std::enable_if_t< !std::is_convertible<decltype(x), To>{}, To> {
throw std::runtime_error("wrong type");
}
},
std::forward<From>( from )
);
}
now that SFINAE is a mess. Let us hide it.
template<class F, class Otherwise>
auto call_or_otherwise( F&& f, Otherwise&& o ) {
return overloaded{
std::forward<F>(f),
[o = std::forward<Otherwise>(o)](auto&&... args)
-> std::enable_if_t< !std::is_invocable< F&, decltype(args)... >{}, std::invoke_result< Otherwise const&, decltype(args)... > >
{ return o( decltype(args)(args)... ); }
};
}
template<class To, class From>
To var2var( From && from )
{
return std::visit(
call_or_otherwise(
[](To to){ return to; },
[](auto&&)->To{ throw std::runtime_error("type mismatch"); }
),
std::forward<From>(from)
);
}
call_or_otherwise takes 2 lambdas (or other callables), and returns one a callable that dispatches to the first if possible, and only falls back on the second if the first fails.
I've written some generic code which manages a list of tuples. Now I want to use that code, but instead of std::tuple I would like to use simple structs, so I can access the variables using names instead of indicies. Is there an easy way to make these structs behave like std::tuple, so I can use it with my generic code?
struct foo {
int x;
float y;
// some code to enable tuple like behavior (e.g. std::get, std::tuple_size)
};
I've tried adding a as_tuple member function which returns all members using std::tie. This works but requires to call this member function at all places where I need the tuple behavior.
The manual way:
struct foo {
int x;
float y;
};
namespace std
{
template <>
class tuple_element<0, foo> {
using type = int;
};
template <>
class tuple_element<1, foo> {
using type = float;
};
template <std::size_t I>
tuple_element_t<I, foo>& get(foo&);
template <>
tuple_element_t<0, foo>& get(foo& f) { return f.x;}
template <>
tuple_element_t<1, foo>& get(foo& f) { return f.y; }
template <std::size_t I>
tuple_element_t<I, foo> get(const foo&);
template <>
tuple_element_t<0, foo> get(const foo& f) { return f.x;}
template <>
tuple_element_t<1, foo> get(const foo& f) { return f.y; }
}
An other way is to write functions as_tuple:
template <typename ... Ts>
std::tuple<Ts...>& as_tuple(std::tuple<Ts...>& tuple) { return tuple; }
std::tuple<int&, float&> as_tuple(foo& f) { return std::tie(f.x, f.y); }
and wrap your call before using tuple-like.
First, as_tuple should be a free function in the namespace of the class. This lets you extend types other people write.
Next, you should attempt to call get in an ADL-enabled context.
using std::get;
auto& x = get<1>(foo);
if you do that we can pull off some magic.
struct get_from_as_tuple {
template<std::size_t I,
class T,
std::enable_if_t< std::is_base_of< get_from_as_tuple, std::decay_t<T> >, bool > = true
>
friend decltype(auto) get( T&& t ) {
return std::get<I>( as_tuple( std::forward<T>(t) ) );
}
};
now
struct foo:get_from_as_tuple {
int x;
float y;
friend auto as_tuple( get_from_as_tuple const& self ) {
return std::tie( self.x, self.y );
}
};
we can do this:
foo f;
using std::get;
std::cout << get<0>(f) << "," << get<1>(f) << "\n";
Now, this still doesn't enable tuple_size and tuple_element.
There is no trivial way to do that part, but we can work around it.
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
namespace tup {
namespace adl_get {
using std::get;
template<std::size_t I,
class T
>
auto get_helper( T&& t )
RETURNS( get<I>(std::forward<T>(t) ) )
}
template<std::size_t I, class T>
auto get( T&& t )
RETURNS(adl_get::get_helper<I>(std::forward<T>(t)))
}
now tup::get<7>( x ) will dispatch to either std::get or another get in x's namespace based off of overload resolution rules.
We can create similar helpers:
namespace util {
template<class T>
struct tag_t {constexpr tag_t(){}};
template<class T>
constexpr tag_t<T> tag{};
}
namespace tup {
namespace adl_tuple_size {
template<class T>
constexpr std::size_t get_tuple_size( tag_t<T>, ... ) {
return std::tuple_size<T>::value;
}
template<class T>
constexpr auto get_tuple_size( tag_t<T>, int )
RETURNS( tuple_size( tag_t<T> ) )
}
template<class T>
constexpr std::size_t tuple_size() {
return adl_tuple_size::get_tuple_size( tag<T> );
}
}
now tup::tuple_size<Foo>() is a constexpr call that gets the size of Foo by either (A) invoking tuple_size( tag_t<Foo> ) in an ADL-enabled context, or (B) returning std::tuple_size<Foo>::value.
Once we have this we can create another helper base type:
struct tuple_size_from_as_tuple {
template<std::size_t I,
class T,
std::enable_if_t< std::is_base_of< get_from_as_tuple, std::decay_t<T> >, bool > = true
>
friend std::size_t tuple_size( T&& t ) {
return std::tuple_size< decltype(as_tuple( std::forward<T>(t) ) ) >::value;
}
};
struct as_tuple_helpers : get_from_as_tuple, tuple_size_from_as_tuple {};
struct foo:as_tuple_helpers {
// ..
};
and we now have 2 primitives.
Repeat this for tag_t<E&> tuple_element( tag_t<T> ). Then we can write a tup::tuple_element<T, 0> alias that dispatches as you like it.
Finally, adapt your existing code that works with std:: tuple facilities to use tup:: facilities. It should work with existing tuple code, and will also work with types inherited from as_tuple_helper which has a friend as_tuple defined.
This does not, however, give you support for structured bindings.
I'm trying to create a tuple analogue in order to access its elements with the corresponding tag-types, not index. I came up with the next solution (simplified):
template<class T> struct tag { using type = T; };
using r = tag<double>;
using t = tag<double>;
using c = tag<int>;
template<class... Ts> class S
{
std::tuple<typename Ts::type&&...> data;
public:
S(typename Ts::type&&... args) : data(std::forward<typename Ts::type>(args)...) {}
};
int main()
{
r::type r0 = 0.;
const t::type t0 = 1.;
auto S0 = S<r, t, c>(r0, t0, 2); // <- error here
//auto T0 = std::forward_as_tuple(r0, t0, 2); // <- works!
}
However it doesn't compile (gcc 7.2):
error: cannot bind rvalue reference of type ‘tag<double>::type&& {aka double&&}’ to lvalue of type ‘tag<double>::type {aka double}’
auto S0 = S<r, t, c>(r0, t0, 2);
^
note: initializing argument 1 of ‘S<Ts>::S(typename Ts::type&& ...) [with Ts = {tag<double>, tag<double>, tag<int>}]’
S(typename Ts::type&&... args) : data(std::forward<typename Ts::type>(args)...) {}
^
I found std::forward_as_tuple function that can deduce the argument types correctly, so my point is do the same for my class. Any hint what I do wrong?
UPD: initial description was incomplete, sorry. My intention is not storing copies, but references (non-const for non-const arguments and const for const- and rvalue-references, similar to what std::forward_as_tuple does). Please, see the comments in the updated code below:
template<class... Ts> class S
{
std::tuple<typename Ts::type...> data;
public:
template<class... Args>
S(Args&&... args) : data(std::forward<Args>(args)...) {}
template<size_t I> auto& get()
{
return std::get<I>(data);
}
};
int main()
{
r::type r0 = 0.;
const t::type t0 = 1.;
auto S0 = S<r, t, c>(r0, t0, 2);
S0.get<0>() = 111; // <- r0 is not changed!
S0.get<1>() = 222; // <- must not be possible!
auto T0 = std::forward_as_tuple(r0, t0, 2);
std::get<0>(T0) = 333; // <- r0 == 333
std::get<1>(T0) = 444; // <- compile error -- can't change const!
}
You need to declare constructor as a template:
#include <utility>
#include <tuple>
template<class T> struct tag { using type = T; };
using r = tag<double>;
using t = tag<double>;
using c = tag<int>;
template<class... Ts> class S
{
std::tuple<typename Ts::type...> data; // there is no need to use && here as we want tuple to contain items "as is", not references
public:
template<typename... TArgs>
S(TArgs && ... args) : data(std::forward<TArgs>(args)...) {}
};
int main()
{
r::type r0 = 0.;
const t::type t0 = 1.;
auto S0 = S<r, t, c>(r0, t0, 2); // <- error here
static_cast<void>(S0); // not used
//auto T0 = std::forward_as_tuple(r0, t0, 2); // <- works!
return(0);
}
Run this code online
I would like to mention another problem: this kind of tuple won't actually let you access elements by tag type as it allows tag duplication. If you need access by tag type you'll need check tag <-> value type associations more thoroughly. You may also want to check some existing tagged-tuple implementation.
Ts::type&& is not a forwarding reference, because Ts::type is not a template parameter, and as such, the && refer to an rvalue reference. You cannot bind an lvalue to an rvalue reference, hence the error.
std::forward_as_tuple works because you are only storing the parameters in a "forwarding tuple", and not storing them in S.
Just change them to get const& reference, as it can also bind to rvalues as well as to lvalues, if you want to store references (I'll assume you do):
template<class... Ts> class S
{
std::tuple<const typename Ts::type&...> data;
public:
S(const typename Ts::type&... args) : data(args...) {}
};
I think I found the solution. The idea is to create an S object using make-function as #VTT suggested. This make-function copies type modifiers (ref, const ref) and added them to the S template parameters. Then, inside S, these type modifiers copied back to the tuple parameters (data) using couple of conditional_t templates. decay_t is used just to get pure type itself, not reference to it (otherwise compiler, gcc72, says error: ‘tag<double>&’ is not a class, struct, or union type; clang38 is a little bit better: : error: type 'tag<double> &' cannot be used prior to '::' because it has no members)
#include <tuple>
#include <utility>
#include <type_traits>
using namespace std;
// additional parameter is needed to distinguish r and t
template<class T, char c> struct tag { using type = T; };
using r = tag<double, 'r'>;
using t = tag<double, 't'>;
using c = tag<int, 'c'>;
//...
namespace details
{
template<size_t N, class T, class... Ts>
struct index_impl {
static constexpr size_t value = N; };
template<size_t I, class T, class Ti, class... Ts>
struct index_impl<I, T, Ti, Ts...> {
static constexpr size_t value =
std::is_same<T, Ti>::value? I : index_impl<I + 1, T, Ts...>::value; };
template<class T, class... Ts>
struct index {
static constexpr size_t value = index_impl<0, T, Ts...>::value; };
template<class T, class... Ts>
static constexpr size_t index_v = index<T, Ts...>::value;
} // namespace details
template<class... Ts> class S
{
std::tuple<
std::conditional_t<
std::is_reference<Ts>::value,
std::conditional_t<
std::is_const<std::remove_reference_t<Ts>>::value,
const typename std::decay_t<Ts>::type&,
typename std::decay_t<Ts>::type&
>,
typename std::decay_t<Ts>::type
>...
> data;
public:
template<class... Args>
S(Args&&... args) : data(std::forward<Args>(args)...) {}
template<class T> auto& get()
{
return std::get<details::index_v<T, std::decay_t<Ts>...>>(data);
}
};
template<class... Ts, class... Args> auto make_S(Args&&... args)
{
return S<
std::conditional_t<
std::is_reference<Args>::value,
std::conditional_t<
std::is_const<std::remove_reference_t<Args>>::value,
const Ts&, Ts&
>,
Ts
>...
>(std::forward<Args>(args)...);
}
int main()
{
r::type r0 = 0;
const t::type t0 = 1;
auto S0 = make_S<r, t, c>(r0, t0, 0);
S0.get<r>() = 111;
//S0.get<t>() = 222; // <- must not be possible!
S0.get<c>() = 333;
auto T0 = std::forward_as_tuple(r0, t0, 2);
std::get<0>(T0) = 444; // <- r0 == 333
//std::get<1>(T0) = 555; // <- compile error -- can't change const!
std::get<2>(T0) = 666;
}
I'm sure the idea can be implemented better, however this solution works! For example I'm not quite understand why is_const doesn't work with references hence I have to eliminate them from the types using remove_reference_t. Thus the final solution is overcomplicated IMO.
I have the following function:
template<class T>
T Check(int index);
How can I write a function, CheckTuple, which, given a tuple type, populates a tuple with calls to Check?
For example:
CheckTuple< std::tuple<int, float, std::string> >()
would return the following tuple:
std::make_tuple( Check<int>(1), Check<float>(2), Check<std::string>(3) )
The other questions I see involve unpacking a given tuple, not building one up this way.
Implementing what you're looking for becomes pretty simple using C++14's integer_sequence. If you don't have that available, here's a C++11 implementation written by Jonathan Wakely.
template<typename Tuple, int... I>
Tuple CallCheck(std::integer_sequence<int, I...>)
{
return std::make_tuple(Check<typename std::tuple_element<I, Tuple>::type>(I)...);
}
template<typename Tuple>
Tuple CheckTuple()
{
return CallCheck<Tuple>(std::make_integer_sequence<int, std::tuple_size<Tuple>::value>());
}
// Use it as
auto tup = CheckTuple<std::tuple<int, float, std::string>>();
Live demo
Here is my working test implementation. (Perhaps someone has an idea of how to improve upon it in terms of conciseness. Can I get rid of TupleInfo somehow?)
#include <typeinfo>
#include <tuple>
#include <iostream>
template<class T>
T Check(int i) {
std::cout << "getting a " << typeid(T).name() << " at position " << i << std::endl;
return T();
}
template<typename Signature>
struct TupleInfo;
template<class T, class... Args>
struct TupleInfo< std::tuple<T, Args...> > {
using Head = T;
using Tail = std::tuple<Args...>;
};
template<int N, class Tuple>
struct TupleChecker {
static Tuple CheckTuple() {
auto t = std::make_tuple(Check<typename TupleInfo<Tuple>::Head>(N));
return std::tuple_cat(t, TupleChecker<N+1, typename TupleInfo<Tuple>::Tail >::CheckTuple());
}
};
template<int N>
struct TupleChecker<N, std::tuple<> > {
static std::tuple<> CheckTuple() {
return std::tuple<>();
}
};
template<class Tuple>
Tuple CheckTuple() {
return TupleChecker<1, Tuple>::CheckTuple();
}
int main() {
std::tuple<> t0 = CheckTuple<std::tuple<> >();
std::tuple<int> t1 = CheckTuple<std::tuple<int> >();
std::tuple<int, float, std::string> t2 = CheckTuple<std::tuple<int, float, std::string> >();
return 0;
}
First off my use case, as I may think in the wrong direction: I want to create a map that maps a value to types. So for example:
Map<std::string> map;
map.insert<int, double, char>("Hey");
auto string = map.at<int, double, char>();
This alone is fairly easy to do with std::type_index. However, I want to add the possibility to match types that are not exact the searched ones, when they are convertible. So the following should also return "Hey", as float can be converted to double:
auto string = map.at<int, float, char>();
I can't use type_index for this case as std::is_convertible only works directly on types. This would be the version without conversion, but as far as it seems it's not easily possible to add conversion handling into it without major changes.
My current attempt looks kind of like the following, please note that this is not working and just shows what I have tried to implement:
template<typename T>
class Map {
T value;
std::vector<Map<T>> children; // all the children of the current node.
// in the above example, if this was
// the int node, the only child
// would be the double node
template<typename T1>
constexpr bool is_convertible() const {
return std::is_convertible<__T__, T1>::value; // this isn't applicable
// since __T__ can't be
// stored (this nodes
// type)
}
public:
template<typename T1, typename... Tn>
void insert(T&& value) {
// iterate through/create the child nodes until the last template param
}
template<typename T1, typename... Tn>
T& at() {
// iterate through thechild nodes until a matching child is found
// either exact match or a convertible
for(auto &c: children) {
// if the above function would work
if(c.template is_convertible<T1>()) {
return c.template at<Tn...>();
}
}
}
}
Now I'm at my wits end how to achieve this. I thought of implementing lambdas as comparator functions, but while the lambda can store the type of the current node, it can't accept a template parameter on call to compare to.
Is there some C+1y generic lambda comparator magic, or even an easier way?
I hope this does what you want, there's ample space for extension and for creating template specialization that attach to any type combination you want. It's not super-pretty, but it can probably be refactored a bit and beautified.
#include <iostream>
template <typename... Args>
struct map {
};
template <>
struct map<int, float, char> {
static constexpr char value[] = "int float char";
};
constexpr char map<int,float,char>::value[];
template <typename T>
struct map<int, T> {
static constexpr typename std::enable_if<std::is_integral<T>::value, char>::type value[] = "int, T";
};
template <typename T>
constexpr typename std::enable_if<std::is_integral<T>::value, char>::type map<int,T>::value[];
int main() {
std::string v = map<int,float,char>::value;
std::string w = map<int,int>::value;
std::string w2 = map<int,unsigned>::value;
// std::string w3 = map<int,float>::value; Won't compile
std::cout << v << "\n";
std::cout << w << "\n";
std::cout << w2 << "\n";
return 0;
}
I wrote some weird code using boost::fusion that comes close to doing what you want:
#include <boost/fusion/container/map.hpp>
#include <boost/fusion/include/insert.hpp>
#include <boost/fusion/include/pair.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <string>
#include <iostream>
#include <tuple>
#include <type_traits>
#include <memory>
template <std::size_t Value1, std::size_t Value2>
struct MinSizeT {
static const std::size_t value = (Value1 > Value2) ? Value2 : Value1;
};
template<typename T1, typename T2, std::size_t N>
struct TupleIsConvertibleHelper {
static const bool value = std::is_convertible<typename std::tuple_element<N - 1, T1>::type, typename std::tuple_element<N - 1, T2>::type>::value && TupleIsConvertibleHelper<T1, T2, N - 1>::value;
};
template<typename T1, typename T2>
struct TupleIsConvertibleHelper<T1, T2, 0> {
static const bool value = true;
};
template<typename T1, typename T2>
bool TupleIsConvertible() { // Return true if all types in T1 are convertible to their corresponding type in T2
if (std::tuple_size<T1>::value != std::tuple_size<T2>::value)
return false;
constexpr std::size_t minSize = MinSizeT<std::tuple_size<T1>::value, std::tuple_size<T2>::value>::value;
return TupleIsConvertibleHelper<T1, T2, minSize>::value;
}
template<typename MapInserter>
class Map {
MapInserter mc;
template<typename... Types>
struct do_at {
template <typename T>
void operator()(T const& x) const { // Find an exact match or the last convertible match
typedef std::tuple<Types...> t1;
typedef typename T::first_type t2;
if (exactMatch)
return;
if (std::is_same<t1, t2>::value) {
exactMatch = true;
value = x.second;
}
else if (TupleIsConvertible<t1, t2>())
value = x.second;
}
mutable bool exactMatch;
mutable typename MapInserter::value_type value;
do_at() : exactMatch(false) {}
};
public:
Map(MapInserter _mc) : mc(_mc) { }
template<typename... Types>
typename MapInserter::value_type at() {
do_at<Types...> res;
boost::fusion::for_each(mc.data->map, res);
return res.value;
}
};
template<typename ValueType, typename MapType = boost::fusion::map<>, typename ParentType = void*>
struct MapInserter {
typedef ValueType value_type;
struct Helper {
MapType map;
std::shared_ptr<ParentType> parent; // Must keep parent alive because fusion is lazy.
Helper() = default;
Helper(MapType&& _map, std::shared_ptr<ParentType> _parent) : map(std::move(_map)), parent(_parent) {}
};
std::shared_ptr<Helper> data;
template<typename... KeyTypes>
auto Insert(ValueType value) -> MapInserter<ValueType, decltype(boost::fusion::insert(data->map, boost::fusion::end(data->map), boost::fusion::make_pair<std::tuple<KeyTypes...>>(value))), Helper> {
auto newMap = boost::fusion::insert(data->map, boost::fusion::end(data->map), boost::fusion::make_pair<std::tuple<KeyTypes...>>(value));
return MapInserter<ValueType, decltype(newMap), Helper>(std::move(newMap), data);
}
MapInserter() : data(std::make_shared<Helper>()) { }
MapInserter(MapType&& _map, std::shared_ptr<ParentType> _parent) : data(std::make_shared<Helper>(std::move(_map), _parent)) {}
MapInserter(MapInserter&&) = default;
MapInserter(const MapInserter&) = default;
};
int main() {
auto mc = MapInserter<std::string>().
Insert<int, char, float>("***int, char, float***").
Insert<float, double>("***float, double***").
Insert<int>("***int***").
Insert<unsigned, bool>("***unsigned, bool***");
Map<decltype(mc)> map(mc);
std::cout << map.at<int, char, float>() << std::endl; // "***int, char, float***"
std::cout << map.at<int, char, double>() << std::endl; // "***int, char, float***"
std::cout << map.at<char>() << std::endl; // "***int***"
return 0;
}
template<class...>struct types { typedef types type; };
template<class T, class types>struct type_index;
template<class T, class...Ts>
struct type_index<T,types<T, Ts...>>:
std::integral_constant<unsigned,0>
{};
template<class T, class T0, class...Ts>
struct type_index<T,types<T0, Ts...>>:
std::integral_constant<unsigned,type_index<T,types<Ts...>::value+1>
{};
template<template<class>class filter, class types_in, class types_out=types<>, class details=void>
struct filter;
template<template<class>class filter, class T0, class... Ts, class... Zs>
struct filter<filter, types<T0,types...>, types<Zs...>,
typename std::enable_if< filter<T0>::value >::type
>: filter<filter, types<types...>, types<Zs...,T0>>
{};
template<template<class>class filter, class T0, class... Ts, class... Zs>
struct filter<filter, types<T0,types...>, types<Zs...>,
typename std::enable_if< !filter<T0>::value >::type
>: filter<filter, types<types...>, types<Zs...>>
{};
template<template<class>class filter, class... Zs>
struct filter<filter, types<>, types<Zs...>,
void
>: types<Zs...>
{};
template<typename T>
struct convertable_to_test {
template<typename U>
using test = std::is_convertible<U, T>;
};
template<class T, class types>
struct get_convertable_to_types:filter< convertable_to_test<T>::template test, types> {};
which is a start.
Create a master types<Ts...> of all of the types your system supports. Call this SupportedTypes.
Map types<Ts...> to std::vector<unsigned> of each type offset in the above list. Now you can store a collection of types at runtime. Call this a runtime type vector.
When adding an entry types<Args...> to the map, run get_convertable_to_types on each type in types<Args...>, and build a cross product in types< types<...>... >. Store the resulting exponential number of runtime type vectors in your implementation details map.
When you query with types<Ts...>, conver to the runtime type vector, and look it up in the implementation details map. And done!
An alternative approach would be to write get_convertable_from_types, and do the mapping to an exponential number of types<Ts...> at the query point, convert each to a runtime type vector. When adding stuff to the map, store only one runtime type vector. This has slower lookup performance, but faster setup performance, and uses far less memory.
I was going to finish this, but got busy.