Search code examples
c++11variadic-templatestype-safety

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?


Solution

  • 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." );