Slartibartfast
Slartibartfast

Reputation: 45

C++11 Check two sets of variadic template arguments match

This question relates to an earlier one I asked regarding implementing something akin to Qt's signal/slots in C++11.

Consider the following (very simplified signal dispatcher, that in this example does nothing of any use, it's just to demonstrate the pattern/problem):

template< typename... TYPES >
class Signal
{
public:
    Signal()  = default;
    ~Signal() = default;

    template< typename... PARAMETERS >
    void broadcast( PARAMETERS &&... p )
    {
        // static_assert to confirm PARAMETERS can map to TYPES
    }
};

This works well enough, but there's some unwanted type conversion going on in practice. e.g.;

// acceptable use.
Signal< int, unsigned, float, char >().broadcast(   1, 2u, 0.f, 'a' );

// should fail compilation, first parameter is a float, 4th is an int.
Signal< int, unsigned, float, char >().broadcast( 0.f,  0, 0.f,   0 );

// acceptable use, first parameter is const, but it's convertible.
const int   i  = 3;
Signal< int, unsigned, float, char >().broadcast(  i, 2u, 0.f, 'a');

// acceptable use, first parameter is const &, but it's convertible.
const int & j = i;
Signal< int, unsigned, float, char >().broadcast(  j, 2u, 0.f, 'a');

There should be no silent float to int conversion. Conversion of const/const & in this instance should be possible (the format of TYPES should not have const or & as all data should be passed by value).

I'd like to prevent compilation where such unwanted type conversion happens. I thought to wrap up both TYPES and PARAMETERS in tuples, iterate over the tuple and confirm that each type in a given tuple parameter index matches (including using std::decay), but then I couldn't see a way to do that at compile time so that it could go in a static_assert.

For reference, compilers of choice are clang (latest on OS X 7.3 (clang-703.0.31)) and vc14.

Is what I want to do possible and, if so, can anyone offer any pointers?

Upvotes: 3

Views: 793

Answers (2)

Quentin
Quentin

Reputation: 63114

Using (once again) the all_true bool pack trick from Columbo:

template <bool...> struct bool_pack;
template <bool... v>
using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>;

template <class... Args>
struct Signal {
    template <class... Dargs, class = typename std::enable_if<all_true<
        std::is_same<Args, typename std::decay<Dargs>::type>{}...
    >{}>::type>
    void broadcast(Dargs &&...) {}
};

This SFINAE's away the function if the parameters don't match exactly.

Upvotes: 4

Arunmu
Arunmu

Reputation: 6901

Here is a metaprogram I quickly came up with. It is a bit coarse, but can be implemented in a more better way. You should probably use the decayed type (std::decay) in the metaprogram to get correct result.

#include <iostream>
#include <type_traits>

template <typename... T> struct param_pack {};

template <typename, typename> struct is_all_same_impl;

template <>
struct is_all_same_impl<param_pack<>, param_pack<>>
{
  static bool const value = true;
};

template <typename T, typename S, typename... Rest, typename... SRest>
struct is_all_same_impl<param_pack<T, Rest...>, param_pack<S, SRest...>>
{
  static bool const value = false;
};

template <typename T, typename... Rest, typename... SRest>
struct is_all_same_impl<param_pack<T, Rest...>, param_pack<T, SRest...>>
{
  static bool const value = is_all_same_impl<param_pack<Rest...>, param_pack<SRest...>>::value;
};

template <typename, typename>
struct is_all_same;

template <typename... FSet, typename... SSet>
struct is_all_same<param_pack<FSet...>, param_pack<SSet...>>: is_all_same_impl<param_pack<FSet...>, param_pack<SSet...>> {};

int main() {
  std::cout << is_all_same<param_pack<int, char, float>, param_pack<int, char, int>>::value << std::endl;
  return 0;
}

UPDATE :: More simpler version

template <typename... T> struct param_pack {};

int main() {
  std::cout << std::is_same<param_pack<int, float, int>, param_pack<int,float,int>>::value << std::endl;
  return 0;
}

So you can do something like:

static_assert( is_same<param_pack<Args...>, param_pack<std::decay_t<Dargs>...>>::value, "Parameters do not sufficiently match." );

Upvotes: 3

Related Questions