Как вы можете статически проверить, что тип T существует в вариативном списке параметров шаблона

Я пытаюсь статически проверить, существует ли тип в списке параметров вариативного шаблона. Однако этот список шаблонов фактически существует в классе, которому передается один тип. Ответ здесь показывает, как проверить список параметров или параметр pack, но я не знаю, как тестировать класс, содержащий шаблоны с переменным числом аргументов.

Например

template <typename ...S>
class Services {};

template <typename Services>
class ServiceLocator
{
public:
      template <typename T>
      T& Resolve() 
      { 
           static_assert( check_t_exists_in_variadic_template_within_Services );
           return Find<T>();  
      }

};

Что я мог бы написать в этом static_assert, чтобы убедиться, что каждый вызов этого локатора службы проверяется и выдается ошибка компилятора, если Resolve вызывается с типом, который не существует в списке параметров шаблона внутри Services?

Что мне особенно нужно, так это что-то вроде:

static_assert(is_any<T,Services::S...>::value, "T does not exist in Services::S");

person makar    schedule 17.10.2013    source источник
comment
Да, я думаю, что это может быть, спасибо. Я потратил целую вечность, пытаясь найти пример этого   -  person makar    schedule 17.10.2013
comment
На самом деле, было бы неплохо посмотреть, как это делается в моем контексте, так как я все еще новичок в синтаксисе вариативного шаблона.   -  person makar    schedule 17.10.2013
comment
@makar Я не совсем понимаю, что мы могли бы поместить в ваш контекст. В моем ответе уже есть пример static_assert, вам нужно только распаковать свои вариативные аргументы Services вместо предоставления фиксированного списка типов, как это сделал я.   -  person syam    schedule 17.10.2013
comment
Добавьте реализацию is_any, а затем выполните static_assert( is_any<T, Services...>::value, "my error message" );   -  person dyp    schedule 17.10.2013
comment
Я обновил свой вопрос чем-то более конкретным   -  person makar    schedule 17.10.2013


Ответы (3)


Основываясь на ответе Франсуа, вот более короткая версия, которая избегает использования std::tuple и использует std::integral_constant (через true/false_type) и предоставляет C++ 14-стиль contains_v псевдоним. Хотя общая идея та же.

template <typename T, typename... Args>
struct contains;

template <typename T>
struct contains<T> : std::false_type {};

template <typename T, typename... Args>
struct contains<T, T, Args...> : std::true_type {};

template <typename T, typename A, typename... Args>
struct contains<T, A, Args...> : contains<T, Args...> {};

template <typename T, typename... Args>
constexpr bool contains_v = contains<T, Args...>::value;

Вы можете использовать его следующим образом:

static_assert(contains_v<float, float, double>,
    "failure: float not among <float, double>"); // does not trigger

static_assert(contains_v<int, float, double>,
    "failure: int not among <float, double>"); // triggers
person kkaefer    schedule 02.09.2016

Одна из проблем с вашим текущим кодом заключается в том, что ServiceLocator принимает конкретный тип, поэтому вы теряете параметры шаблона, переданные в Services. Чтобы получить их, вам нужно каким-то образом указать тип, поэтому я выбрал std::tuple, так как вы не можете напрямую указать тип вариативного списка.

#include <tuple>
#include <type_traits>

template <typename Type, typename Collection>
struct contains;

template <typename Type>
struct contains<Type, std::tuple<>>
{
    typedef std::false_type result;
};

template <typename Type, typename ... Others>
struct contains<Type, std::tuple<Type, Others...>>
{
    typedef std::true_type result;
};

template <typename Type, typename First, typename ... Others>
struct contains<Type, std::tuple<First, Others...>>
{
    typedef typename contains<Type, std::tuple<Others...>>::result result;
};

template <typename ... S>
struct Services
{
    typedef std::tuple<S...> Collection;
};

template <typename ServicesType>
class ServiceLocator
{
public:
    template <typename T>
    T * Resolve()
    {
        static_assert(contains<T, typename ServicesType::Collection>::result::value, "Fail");
        return nullptr;
    }
};

class S1 {};
class S2 {};
class S3 {};

int main(int /*argc*/, char * /*argv*/[])
{
    Services< S1, S2 > services;
    ServiceLocator< decltype(services) > locator;

    locator.Resolve< S1 >();
    locator.Resolve< S2 >();
    locator.Resolve< S3 >(); // triggers static_assert

    return 0;
}

Я только проверил с clang, но я надеюсь, что это поможет.

person François Moisan    schedule 18.10.2013

Вот метод, использующий constexpr:

#include <type_traits>

template <typename T>
constexpr bool contains() {
    return false;
}

template <typename T, typename A, typename... Tail>
constexpr bool contains() {
    return std::is_same<T, A>::value ? true : contains<T, Tail...>();
}

int main()
{
    static_assert(contains<float, int, double, float>(), "does contain float");
    static_assert(contains<float, int, double, char>(), "does not contain float");
}

Помимо того, что он проще и понятнее (IMO), этот метод имеет то преимущество, что его легко расширить для других нужд, поскольку вы можете заменить вызов std::is_same любым другим выражением constexpr bool, например std::is_base_of, чтобы проверить, содержит ли пакет параметров любые базовые или производные типы.

person Asad-ullah Khan    schedule 13.08.2020