用于检查所有模板参数类型是否唯一的编译时 C++ 函数

Compile-time C++ function to check whether all template argument types are unique

有一个很好的问题()提出下一个问题。

需要编写一个编译时函数 template<typename... Ts> constexpr bool allTypesUnique() 如果所有参数类型都是唯一的,它将 return true,否则 false。限制是不成对比较参数类型。不幸的是,答案仅解释了为什么无法通过某些特定方法实现此类功能。

我认为可以使用多重继承来实现解决方案。这个想法是让一个 class 继承自许多 classes:Ts 中的每个类型 T 一个。每个这样的 class 都定义了一个虚函数,其签名取决于 T。如果在 Ts 中多次发现某些 T,则子 class 中的函数 f 将覆盖基 class 中的函数,并且可以检测到:

template<typename> struct A{};

template<typename T, typename... Ts>
struct B : B<Ts...> {
    using B<Ts...>::f;
    constexpr virtual void f(A<T>, bool & unique) { if( ++count > 1 ) unique = false; }
    int count = 0;
};

template<typename T>
struct B<T> {
    constexpr virtual void f(A<T>, bool & unique) { if( ++count > 1 ) unique = false; }
    int count = 0;
};

template<typename... Ts>
constexpr bool allTypesUnique() {
    B<Ts...> b;
    bool res = true;
    ( b.f( A<Ts>{}, res ), ... );
    return res;
}

int main() {
    static_assert( allTypesUnique<void>() );
    static_assert( allTypesUnique<void, int&>() );
    static_assert( !allTypesUnique<int&, int&>() );
    static_assert( allTypesUnique<char, short, int>() );
    static_assert( !allTypesUnique<char, short, char>() );
}

演示:https://gcc.godbolt.org/z/8jhnE7P11

只是好奇,这个解决方案是否正确,是否有更简单的解决方案?

如果根据每个给定类型使用虚拟基础 classes,您将在结果 class 中为每个唯一类型获得精确的基础 class 实例。如果给定类型的数量是生成的基 classes 的数量,则每种类型都是唯一的。您可以通过其大小“测量”生成的基 classes 的数量,但必须注意您有一个 vtable 指针,其大小取决于实现。因此,每个生成的类型都应该足够大以隐藏对齐问题。

顺便说一句:它也适用于引用类型。


template < typename T> struct AnyT { char i[128]; };

template < typename FIRST, typename ... T>
struct CheckT: virtual AnyT<FIRST>, virtual CheckT<T...> { };

template < typename FIRST >
struct CheckT<FIRST>: virtual AnyT<FIRST> {};


template < typename ... T>
constexpr bool allTypesUnique()
{
    using T1 = CheckT<int>;
    using T2 = CheckT<bool, int>;

    constexpr std::size_t s1 = sizeof( T1 );
    constexpr std::size_t s2 = sizeof( T2 );
    constexpr std::size_t diff = s2 - s1; 
    constexpr std::size_t base = s1 - diff;
    constexpr std::size_t measure = sizeof( CheckT< T...> );

    return !((sizeof...(T)*diff+base) - measure);
}


int main() {
    static_assert( allTypesUnique<void>() );
    static_assert( allTypesUnique<void, int>() );
    static_assert( !allTypesUnique<void, void>() );
    static_assert( allTypesUnique<char, short, int>() );
    static_assert( !allTypesUnique<char, short, char>() );
}

Demo

在支持 class 大小优化的编译器中,可以通过 C++20 属性 [[no_unique_address]] 为空成员获得更简单的解决方案。如果所有 class 个空成员都有不同的类型,那么它的 sizeof 将为 1。如果某些成员类型重复,那么它们不能共享相同的地址并且 sizeof 将大于 1。

解决方案代码:

template<typename> struct A{};

template<typename T, typename... Ts>
struct B : B<Ts...> {
    [[no_unique_address]] A<T> a;
};

template<typename T>
struct B<T> {
    [[no_unique_address]] A<T> a;
};

template<typename... Ts>
constexpr bool allTypesUnique() {
    if constexpr (sizeof...(Ts) <= 1 )
        return true;
    else
        return sizeof(B<Ts...>) == 1;
}

int main() {
    static_assert( allTypesUnique<void>() );
    static_assert( allTypesUnique<void, int&>() );
    static_assert( !allTypesUnique<int&, int&>() );
    static_assert( allTypesUnique<char, short, int>() );
    static_assert( !allTypesUnique<char, short, char>() );
}

演示:https://gcc.godbolt.org/z/577EP1774

我认为您可以尝试详细说明使用编译时类型 ID。 这是部分实现的 C++14 版本(需要编译时排序):

#include <cstddef>
#include <type_traits>
#include <utility>

namespace detail {
    template<typename T>
    struct type_id {
        constexpr static int value{};
    };

    template<const int *const A, const int *const B>
    struct same_address : std::false_type {};

    template<const int *const A>
    struct same_address<A, A> : std::true_type {};

}// namespace detail

// TODO: implement
template<const int *const... I>
struct sort_array {
    using type = std::integer_sequence<const int *const, I...>;
};

template<typename>
struct find_duplicates;

template<const int *const A, const int *const B, const int *const... I>
struct find_duplicates<std::integer_sequence<const int *const, A, B, I...>> {
    constexpr static bool value = std::conditional_t<detail::same_address<A, B>::value,
                                                     std::true_type,
                                                     find_duplicates<std::integer_sequence<const int *const, B, I...>>>::value;
};

template<>
struct find_duplicates<std::integer_sequence<const int *const>> {
    constexpr static bool value = false;
};

template<const int *const I>
struct find_duplicates<std::integer_sequence<const int *const, I>> {
    constexpr static bool value = false;
};

template<typename... T>
constexpr bool all_types_unique() {
    return !find_duplicates<typename sort_array<&detail::type_id<T>::value...>::type>::value;
};

int main() {
    static_assert(detail::same_address<&detail::type_id<int>::value,
                                       &detail::type_id<int>::value>::value,
                  "");
    static_assert(!detail::same_address<&detail::type_id<int>::value,
                                        &detail::type_id<double>::value>::value,
                  "");

    static_assert(all_types_unique<>(), "");
    static_assert(all_types_unique<int>(), "");
    static_assert(all_types_unique<int, double>(), "");
    static_assert(all_types_unique<int, double, char>(), "");
    static_assert(!all_types_unique<int, int>(), "");
    static_assert(!all_types_unique<int, int, int>(), "");

    return 0;
}

https://godbolt.org/z/E4G6YchE5

可以构造一个 constexpr 函数来回答这个问题:

template <class T, class... Ts>
constexpr bool unique_types()
{
    if constexpr (sizeof...(Ts)) {
        return !(std::is_same_v<T,Ts> || ...) && unique_types<Ts...>();
    }
    return true;
}

Demo

上面的代码相当于

的编译时间
for (int i(0); i < n; ++i)
  for (int j(i + 1); j < n; ++j)
    { /* check type[i] vs type[j] */ }

如果您想要比较的任何变化(例如考虑 intint& 相同)只需修改 std::is_same_v(例如 std::is_same_v<std::decay_t<T>, std::decay_t<Ts>>