Template class partial specialization for kinds of collections - c++

I'm trying to write 2 template partial specializations which should take care of serializing sequence containers and associative containers differently but it seems like that the sequence container specialization is not used at all since the compiler tries to specialize it with the wrong specialization, here's the code:
template<typename W, typename T, template<typename...> class C, typename... Args>
class Archiver<W, C<T, Args...>>
{
public:
template<typename U = C<T, Args...>, typename std::enable_if<std::is_same<typename U::value_type, T>::value, int>::type = 0>
static void serialize(const W& w, const C<T, Args...>& container)
{
...
}
template<typename U = T, typename std::enable_if<!std::is_base_of<archive::require_placement_move, U>::value, int>::type = 0,
typename J = C<T,Args...>, typename std::enable_if<std::is_same<typename J::value_type, T>::value, int>::type = 0>
static void unserialize(const W& r, const Context& c, C<T,Args...>& container)
{
...
}
template<typename U = T, typename std::enable_if<std::is_base_of<archive::require_placement_move, U>::value, int>::type = 0,
typename J = C<T,Args...>, typename std::enable_if<std::is_same<typename J::value_type, T>::value, int>::type = 0>
static void unserialize(const W& r, const Context& c, C<T,Args...>& container)
{
...
}
};
template< typename W, typename K, typename T, template<typename...> class M, typename... Args>
class Archiver<W, M<K, T, Args...>>
{
public:
template<class U = M<K,T,Args...>, typename std::enable_if<std::is_same<typename U::key_type, K>::value && std::is_same<typename U::mapped_type, T>::value, int>::type = 0>
static void serialize(const W& w, const M<K,T,Args...>& container)
{
...
}
template<class U = M<K,T,Args...>, typename std::enable_if<std::is_same<typename U::key_type, K>::value && std::is_same<typename U::mapped_type, T>::value, int>::type = 0>
static void unserialize(const W& r, const Context& c, M<K,T,Args...>& container)
{
...
}
};
If I try to use
Writer w;
Archiver<Writer, std::list<float>>::serialize(...);
the resolution is done on the second specialization, thus leading to the fact that
Candidate template ignored: substitution failure [with U = std::__1::list<float, std::__1::allocator<float> >]: no type named 'key_type' in 'std::__1::list<float, std::__1::allocator<float> >'
Which should imply that the first specialization is excluded a priori, like if the deduction resolution is choosing the one related to the map. Or maybe I'm missing something really stupid since I'm working on this code since some time.

I think your use of SFINAE is a bit off.
You are explicitly specifying the container type in the Archive template. Some unknown C++ rule means that the second specialization is chosen when you pass std::list<float>. SFINAE is then applied to the serialize function, eliminating the only available overload (hence the error message). Because the second specialization of Archive has been chosen, the serialize function in the first specialization is never even considered. This is not what you want.
Since you are explicitly specifying the container type, I am unsure why you are trying to use SFINAE at all. To properly specialize separately for sequence and associative containers, you will probably have to wait until concepts get standardized. Until then, you can do something naive, like specialize based on the presence of the type alias key_type (which is kind of what you were already doing).
Here is an example:
#include <type_traits>
template<typename>
using void_t = void;
template<typename T, typename = void>
struct has_key_type : std::false_type
{
};
template<typename T>
struct has_key_type<T, void_t<typename T::key_type>> : std::true_type
{
};
template<typename C, typename = std::enable_if_t<has_key_type<C>::value>>
void serialize(C const& c)
{
};
template<typename C, typename = std::enable_if_t<!has_key_type<C>::value>, typename = void>
void serialize(C const& c)
{
};
#include <iostream>
#include <list>
#include <map>
int main()
{
serialize(std::list<int>());
serialize(std::map<float, float>());
}

Related

How can I check if the type `T` is `std::pair<?, bool>` in C++?

We can define a function to insert multiple values to a set like this:
template <typename T, typename... U>
bool insert_all(T& to, const U... arguments) {
return (to.insert(arguments).second && ...);
}
So this code can insert 4, 5 and 6 to the set:
std::set set { 1, 2, 3 };
insert_all(set, 4, 5, 6);
Obviously, type T maybe a type without a method accepting int, called insert and returns std::pair<?, bool>. It's necessary to check it by SFINAE:
template <typename T, typename U, typename = void>
struct can_insert : std::false_type {
};
template <typename T, typename U>
struct can_insert<T, U,
std::enable_if_t<std::is_same_v<decltype(std::declval<T>().insert(std::declval<U>())), std::pair<typename T::iterator, bool>>, void>
> : std::true_type {
};
template <typename T, typename U>
inline constexpr auto can_insert_v = can_insert<T, U>::value;
Pay attention to the second one, ...std::pair<typename T::iterator, bool>>..., how to express it like java code ? extends Pair<?, Boolean> gracefully?
It's not much different than the technique you already coded:
#include <tuple>
#include <type_traits>
template<typename T>
struct is_pair_t_and_bool : std::false_type {};
template<typename T>
struct is_pair_t_and_bool<std::pair<T, bool>> : std::true_type {};
static_assert( is_pair_t_and_bool<std::pair<char, bool>>::value );
static_assert( !is_pair_t_and_bool<int>::value );
static_assert( !is_pair_t_and_bool<std::pair<int, int>>::value );
And, in C++20, once you've gone that far you might as well make it a concept:
template<typename T>
concept pair_t_and_bool=is_pair_t_and_bool<T>::value;
template<pair_t_and_bool T>
struct something {};
something<std::pair<int, bool>> this_compiles;
something<std::pair<int, int >> this_doesnt_compile;
With the help of C++20 concepts, you can define can_insert as concept like:
template<class T, class U>
concept can_insert = std::ranges::range<T> &&
requires(T& to, const U argument) {
{ to.insert(argument) } ->
std::same_as<std::pair<std::ranges::iterator_t<T>, bool>>;
};
Then use it to constrain insert_all like
template<typename T, typename... U>
requires (can_insert<T, U> && ...)
bool insert_all(T& to, const U... arguments) {
return (to.insert(arguments).second && ...);
}

How can you detect whether a call result is valid

I'm trying to create a trait that detects whteher type Apply has a valid result when called with 2 arguments. I would expect the static_assert in the code not to hit because the result of apply is a valid one (dividing float). Why does this assert hit, and how would I change the trait in such a way that all the valid overloads for apply are detected as true_type or constexpr bool true.
#include <type_traits>
#include <functional>
struct Apply
{
template<typename T1, typename T2>
float apply(const T1& a, const T2& b) const
{
return a / b;
}
};
struct ApplyInvoker
{
Apply a;
template<typename... Args>
auto operator()(Args&&... args)
{
return a.apply(std::forward<Args>(args)...);
}
};
template <class Void, class... T>
struct ValidCall : std::false_type
{
};
template <class... T>
struct ValidCall<
std::void_t<decltype(std::invoke(std::declval<ApplyInvoker>(), std::declval<T>()...))>,
T...>
: std::true_type
{
};
template<typename T1, typename T2>
constexpr bool CanApply = ValidCall<void, T1, T2>::value;
int main()
{
static_assert(CanApply<float, float>);
}
You're using your template as:
template<typename T1, typename T2>
constexpr bool CanApply = ValidCall<T1, T2>::value;
But the primary is declared as:
template <class Void, class... T>
struct ValidCall;
That first template parameter is named Void because it has to be void - that's what the specialization is matching against. So you have to do it this way:
template<typename T1, typename T2>
constexpr bool CanApply = ValidCall<void, T1, T2>::value;
// ^~~~
But also, this is C++17, and we have a type trait for that:
template<typename T1, typename T2>
constexpr bool CanApply = std::is_invocable_v<ApplyInvoker, T1, T2>;
The problem is that neither Apply nor ApplyInvoker actually work with type traits. Apply advertises itself as being invocable with any two arguments - there is no way to detect otherwise. ApplyInvoker advertises itself as being invocable with any number of arguments - but in a way that will lead to a hard compiler error if you try to find out with the wrong set of them. Both these types are what we would call SFINAE-unfriendly - they're not friendly to type traits, they're not testable.
If you want them to actually be testable, then you need to rewrite both as follows:
struct Apply
{
template<typename T1, typename T2>
float apply(const T1& a, const T2& b) const
-> decltype(float(a / b))
{
return a / b;
}
};
struct ApplyInvoker
{
Apply a;
template <typename... Args>
auto operator()(Args&&... args)
-> decltype(a.apply(std::forward<Args>(args)...)
{
return a.apply(std::forward<Args>(args)...);
}
};
Or something similar.

How to require an exact function signature in the detection idiom?

Let's suppose I have a type T and I want to detect whether it has a subscript operator which I can call with with another type Index. The following example works just fine:
#include <type_traits>
#include <vector>
template < typename T, typename Index >
using subscript_t = decltype(std::declval<T>()[std::declval<Index>()]);
int main()
{
using a = subscript_t< std::vector<int>, size_t >;
using b = subscript_t< std::vector<int>, int >;
}
However, I want the function to be detected if and only if the function signature matches exactly. In the example above I would like the statement subscript_t< std::vector<int>, int >; to throw an error like no viable overloaded operator[], because the signature of the subscript operator for std::vector is
std::vector<T, std::allocator<T>>::operator[](size_type pos);
where size_type in GCC is unsigned long. How can I avoid the implicit conversion from int to size_t to take place?
With is_detected, you may do:
template <typename T, typename Ret, typename Index>
using subscript_t = std::integral_constant<Ret (T::*) (Index), & T::operator[]>;
template <typename T, typename Ret, typename Index>
using has_subscript = is_detected<subscript_t, T, Ret, Index>;
static_assert(has_subscript<std::vector<int>, int&, std::size_t>::value, "!");
static_assert(!has_subscript<std::vector<int>, int&, int>::value, "!");
Demo
I wrote this in SO Documentation:
is_detected
To generalize type_trait creation:based on SFINAE
there are experimental traits detected_or, detected_t, is_detected.
With template parameters typename Default, template <typename...> Op and typename ... Args:
is_detected: alias of std::true_type or std::false_type depending of the validity of Op<Args...>
detected_t: alias of Op<Args...> or nonesuch depending of validity of Op<Args...>.
detected_or: alias of a struct with value_t which is is_detected, and type which is Op<Args...> or Default depending of validity of Op<Args...>
which can be implemented using std::void_t for SFINAE as following:
namespace detail {
template <class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail
// special type to indicate detection failure
struct nonesuch {
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
template <template<class...> class Op, class... Args>
using is_detected =
typename detail::detector<nonesuch, void, Op, Args...>::value_t;
template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
template <class Default, template<class...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;
Traits to detect presence of method can then be simply implemented:
template <typename T, typename ...Ts>
using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));
struct C1 {};
struct C2 {
int foo(char) const;
};
template <typename T>
using has_foo_char = is_detected<foo_type, T, char>;
static_assert(!has_foo_char<C1>::value, "Unexpected");
static_assert(has_foo_char<C2>::value, "Unexpected");
static_assert(std::is_same<int, detected_t<foo_type, C2, char>>::value,
"Unexpected");
static_assert(std::is_same<void, // Default
detected_or<void, foo_type, C1, char>>::value,
"Unexpected");
static_assert(std::is_same<int, detected_or<void, foo_type, C2, char>>::value,
"Unexpected");
You can do this:
template <typename Index, typename ClassType, typename ReturnType>
constexpr bool arg_t(ReturnType (ClassType::*)(Index))
{
return true;
}
template <typename T, typename Index>
struct subscript_t
{
static_assert(arg_t<Index>(&T::operator[]));
};
Note that you'll need to instantiate subscript_t, not merely name its type:
int main()
{
subscript_t< std::vector<int>, size_t > a;
subscript_t< std::vector<int>, int > b;
}
Another way, which lets you determine the exact error message:
template <typename ClassType, typename ReturnType, typename ArgType>
constexpr ArgType arg_t(ReturnType (ClassType::*)(ArgType))
{
return {};
}
template <typename T, typename Index>
struct subscript_t
{
using Actual = decltype(arg_t(&T::operator[]));
static_assert(std::is_same<Index, Actual>::value, "oops");
};

C++ compile-time predicate to test if a callable object of type F can be called with an argument of type T

I would like to create a compile-type function that, given any callable object f (function, lambda expression, function object, ...) and a type T, evaluates to true, if f can be called with an argument of type T, and false if it cannot.
Example:
void f1(int) { ... }
void f2(const std::string&) { ... }
assert( is_callable_with<int>(f1));
assert(!is_callable_with<int>(f2));
I'm thinking that a clever use of the SFINAE rule could achieve this. Possibly somehow like this:
template<typename T, typename F>
constexpr bool is_callable_with(F&&, typename std::result_of<F(T)>::type* = nullptr) {
return true;
}
template<typename T, typename F>
constexpr bool is_callable_with(F&&) {
return false;
}
But this doesn't work, because if F is callable with T, both overloads participate in the overload resolution and there is an ambiguity. I'd like to rewrite it so in the positive case, the first overload would be picked by the overload resolution over the second one. Not sure if I'm even on the right track here though.
A variant of Paul's answer, but following the standard SFINAE test pattern. Again a generic trait with arbitrary parameter types A...:
struct can_call_test
{
template<typename F, typename... A>
static decltype(std::declval<F>()(std::declval<A>()...), std::true_type())
f(int);
template<typename F, typename... A>
static std::false_type
f(...);
};
template<typename F, typename... A>
using can_call = decltype(can_call_test::f<F, A...>(0));
Then a constexpr function as you requested:
template<typename T, typename F>
constexpr bool is_callable_with(F&&) { return can_call<F, T>{}; }
Check live example.
This will work with functions, lambda expressions, or function objects with arbitrary number of arguments, but for (pointers to) member functions you'll have to use std::result_of<F(A...)>.
UPDATE
Below, can_call has the nice "function signature" syntax of std::result_of:
template<typename F, typename... A>
struct can_call : decltype(can_call_test::f<F, A...>(0)) { };
template<typename F, typename... A>
struct can_call <F(A...)> : can_call <F, A...> { };
to be used like this
template<typename... A, typename F>
constexpr can_call<F, A...>
is_callable_with(F&&) { return can_call<F(A...)>{}; }
where I've also made is_callable_with variadic (I can't see why it should be limited to one argument) and returning the same type as can_call instead of bool (thanks Yakk).
Again, live example here.
I would make a type trait first:
template<class X = void>
struct holder
{
typedef void type;
};
template<class F, class T, class X = void>
struct is_callable_with_trait
: std::false_type
{};
template<class F, class T>
struct is_callable_with_trait<F, T, typename holder<
decltype(std::declval<F>()(std::declval<T>()))
>::type>
: std::true_type
{};
And then if you want, you can turn it into a function:
template<typename T, typename F>
constexpr bool is_callable_with(F&&)
{
return is_callable_with_trait<F&&, T>::value;
}
template<class F, class T, class = void>
struct is_callable_with_impl : std::false_type {};
template<class F, class T>
struct is_callable_with_impl<F,T,
typename std::conditional<
true,
void,
decltype( std::declval<F>() (std::declval<T>()) ) >::type
> : std::true_type {};
template<class T, class F>
constexpr bool is_callable_with(F &&)
{
return is_callable_with_impl< F, T >::value;
}
It is basically the same solution as the one posted by Paul, I just prefer to use conditional<true, void, decltype( ... ) > instead of an holder class to avoid namespace pollution.

Reordering Variadic Parameters

I have come across the need to reorder a variadic list of parameters that is supplied to the constructor of a struct. After being reordered based on their types, the parameters will be stored as a tuple. My question is how this can be done so that a modern C++ compiler (e.g. g++-4.7) will not generate unnecessary load or store instructions. That is, when the constructor is invoked with a list of parameters of variable size, it efficiently pushes each parameter into place based on an ordering over the parameters' types.
Here is a concrete example. Assume that the base type of every parameter (without references, rvalue references, pointers, or qualifiers) is either char, int, or float. How can I make it so that all the parameters of base type char appear first, followed by all of those of base type int (which leaves the parameters of base type float last). The relative order in which the parameters were given should not be violated within sublists of homogeneous base type.
Example: foo::foo() is called with arguments float a, char&& b, const float& c, int&& d, char e. The tuple tupe is std::tuple<char, char, int, float, float>, and it is constructed like so: tuple_type{std::move(b), e, std::move(d), a, c}.
Consider the struct defined below, and assume that the metafunction deduce_reordered_tuple_type is already implemented. How would you write the constructor so that it works as intended? If you think that the code for deduce_reodered_tuple_type, would be useful to you, I can provide it; it's a little long.
template <class... Args> struct foo
{
// Assume that the metafunction deduce_reordered_tuple_type is defined.
typedef typename deduce_reordered_tuple_type<Args...>::type tuple_type;
tuple_type t_;
foo(Args&&... args) : t_{reorder_and_forward_parameters<Args>(args)...} {}
};
Edit 1
The technique I describe above does have applications in mathematical frameworks that make heavy use of expression templates, variadic templates, and metaprogramming in order to perform aggressive inlining. Suppose that you wish to define an operator that takes the product of several expressions, each of which may be passed by reference, reference to const, or rvalue reference. (In my case, the expressions are conditional probability tables and the operation is the factor product, but something like matrix multiplication works suitably as well.)
You need access to the data provided by each expression in order to evaluate the product. Consequently, you must move the expressions passed as rvalue references, copy the expressions passed by reference to const, and take the addresses of expressions passed by reference. Using the technique I describe above now poses several benefits.
Other expressions can use uniform syntax to access data elements from this expression, since all of the heavy-lifting metaprogramming work is done beforehand, within the class.
We can save stack space by grouping the pointers together and storing the larger expressions towards the end of the tuple.
Implementing certain types of queries becomes much easier (e.g. check whether any of the pointers stored in the tuple aliases a given pointer).
Thank you very much for your help!
Happy 4th of July everyone! Ok, here you go.
It turns out that g++-4.7 is pretty awesome at iniling and creates identical machine code according to my testing (instructions to reproduce results are below the code).
#include <iostream>
#include <tuple>
#include <typeinfo>
#include <type_traits>
template <class... Args>
struct sequence
{
typedef std::tuple<Args...> tuple_type;
};
template <class U, class V>
struct sequence_cat;
template <class... U, class... V>
struct sequence_cat<sequence<U...>, sequence<V...>>
{
typedef sequence<U..., V...> type;
};
template <class... V>
struct sequence_cat<void, sequence<V...>>
{
typedef sequence<V...> type;
};
template <class... U>
struct sequence_cat<sequence<U...>, void>
{
typedef sequence<U...> type;
};
template <>
struct sequence_cat<void, void>
{
typedef void type;
};
template <class T>
struct undecorate
{
typedef typename std::remove_reference<T>::type noref_type;
typedef typename std::remove_pointer<noref_type>::type noptr_type;
typedef typename std::remove_cv<noptr_type>::type type;
};
template <class T>
struct deduce_storage_type
{
typedef T type;
};
template <class T>
struct deduce_storage_type<T&>
{
typedef T* type;
};
template <class T>
struct deduce_storage_type<const T&>
{
typedef T type;
};
template <class T>
struct deduce_storage_type<T&&>
{
typedef T type;
};
template <class T, class... Args>
struct filter_type;
template <class T, class Arg, class... Args>
struct filter_type<T, Arg, Args...>
{
static constexpr bool pred =
std::is_same<typename undecorate<Arg>::type, T>::value;
typedef typename deduce_storage_type<Arg>::type storage_type;
typedef typename
std::conditional<
pred,
typename sequence_cat<
sequence<storage_type>,
typename filter_type<T, Args...>::type
>::type,
typename filter_type<T, Args...>::type
>::type type;
};
template <class T, class Arg>
struct filter_type<T, Arg>
{
static constexpr bool pred =
std::is_same<typename undecorate<Arg>::type, T>::value;
typedef typename deduce_storage_type<Arg>::type storage_type;
typedef typename
std::conditional<pred, sequence<storage_type>, void>::type
type;
};
template <class... Args>
struct deduce_sequence_type
{
typedef typename filter_type<char, Args...>::type char_sequence;
typedef typename filter_type<int, Args...>::type int_sequence;
typedef typename filter_type<float, Args...>::type float_sequence;
typedef typename
sequence_cat<
char_sequence,
typename sequence_cat<
int_sequence,
float_sequence
>::type
>::type type;
};
template <class T>
struct get_storage_type
{
static T apply(T t) { return t; }
};
template <class T>
struct get_storage_type<T&>
{
static T* apply(T& t) { return &t; }
};
template <class T>
struct get_storage_type<const T&>
{
static T apply(const T& t) { return t; }
};
template <class T>
struct get_storage_type<T&&>
{
static T&& apply(T&& t) { return std::move(t); }
};
template <class T, class Arg>
struct equals_undecorated_type
{
static constexpr bool value =
std::is_same<typename undecorate<Arg>::type, T>::value;
};
template <bool Pred, bool IsNextVoid, class T, class... Args>
struct filter_parameter_impl;
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<false, false, T, Arg1, Arg2, Args...>
{
typedef typename filter_type<T, Arg2, Args...>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static constexpr bool pred = equals_undecorated_type<T, Arg2>::value;
static constexpr bool is_next_next_void =
std::is_same<typename filter_type<T, Args...>::type, void>::value;
static tuple_type apply(Arg1&&, Arg2&& arg2, Args&&... args)
{
return filter_parameter_impl<
pred, is_next_next_void, T, Arg2, Args...
>::apply(
std::forward<Arg2>(arg2),
std::forward<Args>(args)...
);
}
};
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<false, true, T, Arg1, Arg2, Args...>
{
static void apply(Arg1&&, Arg2&&, Args&&...) {}
};
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<true, false, T, Arg1, Arg2, Args...>
{
typedef typename
filter_type<T, Arg1, Arg2, Args...>::type
sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static constexpr bool pred = equals_undecorated_type<T, Arg2>::value;
static constexpr bool is_next_next_void =
std::is_same<typename filter_type<T, Args...>::type, void>::value;
static tuple_type apply(Arg1&& arg1, Arg2&& arg2, Args&&... args)
{
return std::tuple_cat(
std::make_tuple(get_storage_type<Arg1>::apply(arg1)),
filter_parameter_impl<
pred, is_next_next_void, T, Arg2, Args...
>::apply(
std::forward<Arg2>(arg2),
std::forward<Args>(args)...
)
);
}
};
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<true, true, T, Arg1, Arg2, Args...>
{
typedef typename filter_type<T, Arg1>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&& arg1, Arg2&&, Args&&...)
{
return std::make_tuple(get_storage_type<Arg1>::apply(
std::forward<Arg1>(arg1)
));
}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<false, false, T, Arg1, Arg2>
{
typedef typename filter_type<T, Arg2>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&&, Arg2&& arg2)
{
return std::make_tuple(get_storage_type<Arg2>::apply(
std::forward<Arg2>(arg2)
));
}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<false, true, T, Arg1, Arg2>
{
static void apply(Arg1&&, Arg2&&) {}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<true, false, T, Arg1, Arg2>
{
typedef typename filter_type<T, Arg1>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&& arg1, Arg2&& arg2)
{
return std::make_tuple(
get_storage_type<Arg1>::apply(std::forward<Arg1>(arg1)),
get_storage_type<Arg2>::apply(std::forward<Arg2>(arg2))
);
}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<true, true, T, Arg1, Arg2>
{
typedef typename filter_type<T, Arg1, Arg2>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&& arg1, Arg2&&)
{
return std::make_tuple(
get_storage_type<Arg1>::apply(std::forward<Arg1>(arg1))
);
}
};
template <class T, class... Args>
struct filter_parameter;
template <class T, class Arg, class... Args>
struct filter_parameter<T, Arg, Args...>
{
typedef typename filter_type<T, Arg, Args...>::type sequence_type;
typedef typename std::conditional<
std::is_same<sequence_type, void>::value,
void,
typename sequence_type::tuple_type
>::type tuple_type;
static constexpr bool pred = equals_undecorated_type<T, Arg>::value;
static constexpr bool is_next_void =
std::is_same<typename filter_type<T, Args...>::type, void>::value;
static tuple_type apply(Arg&& arg, Args&&... args)
{
return filter_parameter_impl<
pred, is_next_void, T, Arg, Args...
>::apply(std::forward<Arg>(arg), std::forward<Args>(args)...);
}
};
template <bool Is1Void, bool Is2Void, bool Is3Void, class... Args>
struct get_tuple_impl;
template <class... Args>
struct get_tuple_impl<false, false, false, Args...>
{
typedef typename deduce_sequence_type<Args...>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Args&&... args)
{
return std::tuple_cat(
filter_parameter<char, Args...>::apply(
std::forward<Args>(args)...
),
filter_parameter<int, Args...>::apply(
std::forward<Args>(args)...
),
filter_parameter<float, Args...>::apply(
std::forward<Args>(args)...
)
);
}
};
template <class... Args>
struct get_tuple
{
typedef typename deduce_sequence_type<Args...>::type sequence_type;
typedef typename std::conditional<
std::is_same<sequence_type, void>::value,
void,
typename sequence_type::tuple_type
>::type tuple_type;
static constexpr bool is1void =
std::is_same<typename filter_type<char, Args...>::type, void>::value;
static constexpr bool is2void =
std::is_same<typename filter_type<int, Args...>::type, void>::value;
static constexpr bool is3void =
std::is_same<typename filter_type<float, Args...>::type, void>::value;
static tuple_type apply(Args&&... args)
{
return get_tuple_impl<is1void, is2void, is3void, Args...>::
apply(std::forward<Args>(args)...);
}
};
template <class... Args>
struct foo
{
typedef typename deduce_sequence_type<Args...>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
tuple_type t_;
foo(Args&&... args) : t_{} {}
};
int main()
{
char a = 5;
const int b = 6;
float c = 7;
int d = 8;
float e = 9;
char f = 10;
auto x = get_tuple<char&, const int&, float&, int&, float&&, char&>::
apply(a, b, c, d, std::move(e), f);
//std::tuple<char*, char*, int, int*, float*, float> x{&a, &f, b, &d, &c, std::move(f)};
std::cout << typeid(x).name() << std::endl;
return 0;
}
In order to reproduce the results, do the following (assuming you have g++-4.7 installed).
g++ -std=c++11 -Wall -Ofast -march=native variadic_reorder.cpp -S -o with_templates.s
// Comment out the line in main, and comment the line containing auto x, as well as the line below it.
g++ -std=c++11 -Wall -Ofast -march=native variadic_reorder.cpp -S -o without_templates.s
vimdiff with_templates.s without_templates.s
The only differences I noticed were things like the names of labels; otherwise, the generated machine code was identical.
Edit 1
I'm going to accept my own answer until someone else posts something more elegant than what I have.
I don't have time to experiment with this, but if your compiler is generating too many moves when permuting forwarded arguments, I would recommend forwarding through std::forward_as_tuple. The tuple is a data structure with a concrete layout, and constructing it encourages the compiler to put things into memory all at once, in the order you want.
On the other hand, it is less likely to promote arguments to registers and bypass the stack for simple functions. And nothing is really guaranteed as long as the tuple is only used as a pure value (no reference is taken), because under the as-if rule there is no need for its members to have any addresses.
We can save stack space by grouping the pointers together and storing the larger expressions towards the end of the tuple.
Lvalue references are implemented as pointers by the ABI yet you specified to group them as data values. Rvalue references should be treated the same as lvalue references if you want to adhere to the native passing semantics. (I assume you will only be moving class types.) So the sorting problem is slightly more complicated than stated, because you want to sort the parameters into value and pointer categories, then sort those by base type.
As for the sorting algorithm itself, I would try just popping from the input pack and pushing to a set of output tuples, queue-style, and then catenating the output tuples with std::tuple_cat. That will be the simplest to implement, stable, and should hit the compiler's common-case optimizations. Don't implement an algorithm intended to run in-place in memory because TMP doesn't work like that.
As for translating the results of sorting into the function that permutes the parameters into the arguments to forward_as_tuple, I'm not so sure. You'll probably have to deal with indexes.
You want to be very sure the benefit is worth it before committing to all this.