Как проверить, может ли тип шаблона быть создан из заданного типа - PullRequest
0 голосов
/ 24 января 2020

Я пытаюсь использовать std::enable_if_t для включения класса, если параметр шаблона имеет любой тип std::string (бонусные баллы за ответ, который показывает, как это сделать с общей строкой из нескольких библиотек).

Я не хочу делать «если Text - это const char*, std::string, std::string_view, et c». По сути, я не хочу специально упоминать все возможные строковые объекты. Все, что я собираюсь сделать с этим объектом, это распечатать его на консоли, и объект Text будет сохранен как атрибут класса.

Есть ли более элегантный способ сделать это, чем учет каждого отдельного человека тип

Ответы [ 2 ]

1 голос
/ 24 января 2020

Вы можете использовать is_detected для этого. Мы пытаемся проверить, можно ли печатать данный тип с одной из перегрузок std::cout s, равной operator<<, или сам тип предоставляет operator<< для печати на std::cout. Для более общего объяснения того, как я это реализовал, посмотрите https://www.fluentcpp.com/2017/06/02/write-template-metaprogramming-expressively/

Сначала мы определим соответствующий is_detected для перегрузок самого std :: cout:

// check std::cout.operator<<(T {})
template<typename = void, typename Arg = void> struct test_operator_of_cout : std::false_type {};

template<typename Arg>
struct test_operator_of_cout<std::void_t<decltype(std::cout.operator<<(std::declval<Arg>()))>, Arg>
    : std::true_type {};

template<typename Arg>
constexpr bool test_operator_of_cout_v = test_operator_of_cout<void, Arg>::value;

И еще один для всех перегрузок operator<<(ostream&, T {}). Ссылка, которую я разместил выше, обобщает это, чтобы иметь меньшую избыточность кода.

// check operator<<(std::cout, T {})
template<typename = void, typename Arg = void> struct test_operator_of_struct : std::false_type {};

template<typename Arg>
struct test_operator_of_struct<std::void_t<decltype(operator<<(std::cout, std::declval<Arg>()))>, Arg>
    : std::true_type {};

template<typename Arg>
constexpr bool test_operator_of_struct_v = test_operator_of_struct<void, Arg>::value;

Теперь мы можем использовать эти черты типа для реализации функции печати с помощью enable_if:

template<typename T> struct MyClass {

    T t;

    template<
        typename Consider = T,
        typename = std::enable_if_t<
            ( test_operator_of_cout_v<Consider> || test_operator_of_struct_v<Consider>) 
            && !std::is_arithmetic_v<Consider>
        >
    > void print() {
        std::cout << t;
    }

};

Есть две вещи отметить здесь:

  • Вам нужен первый аргумент шаблона Consider = T. В противном случае компилятор попытается создать экземпляр объявления функции, который не подходит для типов, которые не выполняют условие. Посмотрите этот SO-ответ для более подробного объяснения: std :: enable_if для условной компиляции функции-члена
  • Арифметические типы c не печатаются из-за !std::is_arithmetic , Я лично не включил бы это, потому что я не вижу причины, по которой мой класс не должен позволять печатать идеально печатные типы.

Теперь мы можем посмотреть, что можно печатать, а что нет:

struct NotPrintable {};

struct Printable {
    friend std::ostream& operator<<(std::ostream& os, const Printable& p) {
         return os;
    }
};

auto foo() {

    MyClass<const char *> chars;
    chars.print(); //compiles

    MyClass<std::string> strings;
    strings.print(); //compiles

    MyClass<std::string_view> string_views;
    string_views.print(); //compiles

    MyClass<Printable> printables;
    printables.print(); // compiles

    // MyClass<int> ints;
    // ints.print(); // Does not compile due to !is_arithmetiv_v

    // MyClass<NotPrintable> not_printable;
    // not_printable.print(); //Does not compile due to operator checking

}

Вы можете посмотреть полный пример здесь: https://godbolt.org/z/ZC9__e

0 голосов
/ 24 января 2020

Оформить заказ библиотеки type_traits:

https://en.cppreference.com/w/cpp/header/type_traits

Возможно, вы заинтересованы в is_constructible, is_convertible или is_assignable в зависимости от точного метод, который вы хотите использовать.

Например, is_constructible<T, Args...> проверяет наличие конструктора T::T(Args...).

is_convertible<From, To> проверяет, можно ли преобразовать To в From: например, следующий код компилирует From Y = (From)X;, где X является To

...