Search code examples
c++variadic-templates

Check the sizeof of all variadic arguments is the same


I would like to be able to check, at compile-time using static_assert, that all variadic arguments to a class are created with the same size.

Here is the code I have come up with, but it doesn't work:

template<typename... Args>
class MyClass {
    static size_t arg_reference_size;

    constexpr static bool size_is_equal() {
        return true;
    }

    template<typename First, typename... Others>
    constexpr static bool size_is_equal() {
        return arg_reference_size == sizeof(First) && size_is_equal<Others...>();
    }

    template<class First, typename... Others>
    constexpr static bool check_args_size() {
        arg_reference_size = sizeof(First);
        return size_is_equal<Others...>();
    }

    static_assert(size_is_equal<Args...>(), "");
}

Solution

  • I'd move the type trait out of the class:

    template<class T, class... R>
    inline constexpr bool is_same_size_v = (... && (sizeof(T) == sizeof(R)));
    
    template<typename... Args>
    class MyClass {
        static_assert(is_same_size_v<Args...>);
    };
    
    int main() {
        MyClass<int, unsigned> x;
        //MyClass<char, long double> y; // will very likely fail
    }
    

    If you'd like to keep the type trait internal to your class and build on your current idea, you could make sure that size_is_equal() is declared to return a type with a static constexpr variable set to true or false depending on the template parameters. You don't need an actual implementation of the function. The returned type will be enough. Here std::integral_constant<bool, true> or std::integral_constant<bool, false> will be returned - and they are different types with a static constexpr bool value variable. You can then use decltype(<the returned type>)::value to get to the actual result.

    template<typename... Args>
    class MyClass {
        template<typename First, typename... Others>
        static auto size_is_equal() ->
            std::integral_constant<bool, (... && (sizeof(First) == sizeof(Others)))>;
    
        static constexpr bool size_is_equal_v =
            decltype(size_is_equal<Args...>())::value;
    
        static_assert(size_is_equal_v, "nope");
    };
    

    A third option, if you really want your functions to have implementations and if you can't use C++17 fold expressions and need recursion instead:

    #include <type_traits>
    
    template <typename... Args>
    class MyClass {
        template <typename... Ts>
        // SFINAE to only make this match if the param pack is empty:
        constexpr static typename std::enable_if<sizeof...(Ts) == 0, bool>::type
        size_is_equal() {
            return true;  // or `false` if an empty parameter pack should not be
                          // allowed
        }
    
        template <typename>  // a single parameter to terminate recursion
        constexpr static bool size_is_equal() {
            return true; // a single parameter is always true
        }
    
        // Take at least two template parameters and pass one of them on to the
        // recursive call:
        template <typename First, typename Second, typename... Others>
        constexpr static bool size_is_equal() {
            return sizeof(First) == sizeof(Second) &&
                   size_is_equal<Second, Others...>();
        }
    
        static_assert(size_is_equal<Args...>(), "not equal size");
    };