Отклонить типы, которые содержат определенные открытые статические члены данных - PullRequest
1 голос
/ 10 ноября 2019

Я хочу написать шаблонную функцию, которая отклоняет типы, которые имеют определенные общедоступные статические члены данных.

Один из способов добиться этого - использовать is_detected на всех нежелательных членах.

Mycode:

#include <experimental/type_traits>

template <typename T>
using DetectA = decltype(T::A);

template <typename T>
using DetectB = decltype(T::B);

template <typename T>
void foo()
{
    static_assert(
        !std::experimental::is_detected<DetectA, T>::value);
    static_assert(
        !std::experimental::is_detected<DetectB, T>::value);
}

struct U
{
    static constexpr auto A = false;
    static constexpr auto B = true;
};

struct V
{
    static constexpr auto C = false;
    static constexpr auto D = true;
};

void bar()
{
    // foo<U>();  // <- Throws compiler error as it should
    foo<V>();
}

Как вы можете себе представить, код быстро становится повторяющимся, если у меня есть другие участники, которых нужно отклонить. Я должен быть в состоянии обойти это ограничение, если у меня есть одна операция обнаружения, которая сообщает мне, если any из членов данных присутствует.

Обратите внимание, что приведенный ниже код не работает, так как я хочу отклонить, еслиany участник присутствует, а не all.

template <typename T>
using DetectAorB = decltype(T::A, T::B);  // <- This is and, not or

template <typename T>
void baz()
{
    static_assert(
        !std::experimental::is_detected<DetectAorB, T>::value);
}

struct W
{
    static constexpr auto B = false;
};

void qux()
{
    // baz<U>();
    baz<V>();
    baz<W>();  // <- This shouldn't compile
}

Как мне добиться этого поведения? Использование is_detected не обязательно, но полезно знать, как я могу обнаружить такие случаи. Я использую C ++ 17 (если эта информация полезна).

Ответы [ 2 ]

1 голос
/ 10 ноября 2019

Давайте использовать std::disjunction для логических и вариационных шаблонов «ИЛИ»:

template<class T>
using detect_a = decltype(T::a);

template<class T>
using detect_b = decltype(T::b);

template<class T, template<class> class detector, typename = void>
struct is_detected : std::false_type {};

template<class T, template<class> class detector>
struct is_detected<T, detector, std::void_t<detector<T>>> : std::true_type {};

template<class T, template<class> class... detectors>
struct is_any_detected : std::disjunction<is_detected<T, detectors>...> {};

template<class T>
void foo() {
    static_assert(!is_any_detected<T, detect_a, detect_b>::value);
}

Тогда:

struct U {
    static constexpr auto a = false;
    static constexpr auto b = true;
};

struct V {
    static constexpr auto c = false;
    static constexpr auto d = true;
};

struct W {
    static constexpr auto b = false;
};

foo<U>(); // doesn't compile
foo<V>(); // compiles
foo<W>(); // doesn't compile

Демонстрация

1 голос
/ 10 ноября 2019

Хитрость с шаблонами заключается в том, что заменой должно быть имя или значение реального типа, а не membername. Таким образом, этот подход всегда будет иметь некоторый шаблон.

Макросы не имеют этого ограничения, но они плохо подходят, потому что вариационные макросы не могут быть рекурсивными. НО, как и в старых шаблонах C ++ 98, вы все равно можете принимать решения «до N». Я взял трещину при создании до 4 членов проверки. Вы можете растянуть этот шаблон, если вам нужно больше.

#include <type_traits>
#include <utility>

#define CHECK_MEMBER( type, member, member_type ) \
  template <typename _T, typename = void> \
  struct __has_##type##member : std::false_type { }; \
  template <typename _T> \
  struct __has_##type##member<_T, std::enable_if_t< std::is_same_v<member_type*, decltype( &_T::member )>>> : std::true_type { }; \
  static_assert(!__has_##type##member<type>(), "forbidden " #member_type " member '" #member "' in type '" #type "'");

#define _CHECK_MEMBER_1( _1 ) static_assert(false, "Expected at least 3 arguments, found 1")
#define _CHECK_MEMBER_2( _1, _2 ) static_assert(false, "Expected at least 3 arguments, found 2")
#define _CHECK_MEMBER_3( _1, _2, _3 ) CHECK_MEMBER(_1, _2, _3)
#define _CHECK_MEMBER_4( _1, _2, _3, _4 ) static_assert(false, "Expected odd number of arguments, found 4")
#define _CHECK_MEMBER_5( _1, _2, _3, _4, _5 ) CHECK_MEMBER(_1, _2, _3) CHECK_MEMBER(_1, _4, _5)
#define _CHECK_MEMBER_6( _1, _2, _3, _4, _5, _6 ) static_assert(false, "Expected odd number of arguments, found 6")
#define _CHECK_MEMBER_7( _1, _2, _3, _4, _5, _6, _7 ) CHECK_MEMBER(_1, _2, _3) CHECK_MEMBER(_1, _4, _5) CHECK_MEMBER(_1, _6, _7)
#define _CHECK_MEMBER_8( _1, _2, _3, _4, _5, _6, _7, _8 ) static_assert(false, "Expected odd number of arguments, found 8")
#define _CHECK_MEMBER_9( _1, _2, _3, _4, _5, _6, _7, _8, _9 ) CHECK_MEMBER(_1, _2, _3) CHECK_MEMBER(_1, _4, _5) CHECK_MEMBER(_1, _6, _7) CHECK_MEMBER(_1, _8, _9)

#define _GET_CHECK_MEMBER(_1,_2,_3,_4,_5,_6,_7,_8,_9,NAME,...) NAME
#define CHECK_MEMBERS(...) _GET_CHECK_MEMBER(__VA_ARGS__, _CHECK_MEMBER_9, _CHECK_MEMBER_8, _CHECK_MEMBER_7, _CHECK_MEMBER_6, _CHECK_MEMBER_5, _CHECK_MEMBER_4, _CHECK_MEMBER_3, _CHECK_MEMBER_2, _CHECK_MEMBER_1)(__VA_ARGS__)

Использование:

struct BadType { static int bad1; static bool* bad2; static double** bad3; static double bad4; };
struct OkNotStaticType { int bad1; bool* bad2; double** bad3; double bad4; };
struct OkDifferentType { double bad1; void* bad2; char* bad3; float bad4; };
struct OkUnrelatedType { bool good; };

#define CHECK_FORBIDDEN(T) CHECK_MEMBERS(T, bad1, int, bad2, bool*, bad3, double**, bad4, double)
CHECK_FORBIDDEN(BadType) // "forbidden int member 'bad1' in type 'BadType'"
                         // "forbidden bool* member 'bad2' in type 'BadType'"
                         // "forbidden double** member 'bad3' in type 'BadType'"
                         // "forbidden double member 'bad4' in type 'BadType'"
CHECK_FORBIDDEN(OkNotStaticType)
CHECK_FORBIDDEN(OkDifferentType)
CHECK_FORBIDDEN(OkUnrelatedType)

Пример:

https://godbolt.org/z/-bpbTg

...