C ++ выражение SFINAE и ostream манипуляторы - PullRequest
5 голосов
/ 30 января 2020

Я пытаюсь научиться использовать SFINAE. В практических целях я пытался создать оболочку std::ostream для создания пользовательского форматера.

Вот мой SFINAE и пользовательский класс вывода.

// Tester
template <class O>
struct is_ostreamable {
    template <class T>
    static auto check(T t) -> decltype(std::declval<std::ostream &>() << t, std::true_type());
    template <class>
    static auto check(...) -> std::false_type;

  public:
    static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};

// Custom class
struct CustomOutput {
    // Constructor etc...
    CustomOutput(std::ostream &os = std::cout) : os{os} {}
    std::ostream &os;

    // Problematic template function
    template <class O, class = std::enable_if_t<is_ostreamable<O>::value>>
    CustomOutput &operator<<(O o) {
        os << o;
        return *this;
    }
};

Он идеально подходит для не включить шаблон для struct или class, который нельзя распечатать с помощью operator<<. Однако с этим SFINAE не работают манипуляторы ostream ... И я не могу понять, почему.

Ошибка и мои ожидания:

int main(void){
    CustomOutput{} << "hi"; // Fine

    std::vector<int> vec;
    // CustomOutput{} << vec; // Error. Expected

    CustomOutput{} << std::endl; // Error. WHY?
}

Может быть, я что-то пропустил? Любая помощь будет принята с благодарностью.

Ответы [ 2 ]

3 голосов
/ 30 января 2020

Во-первых, исправьте свой ostreamable класс. В настоящее время вашему классу требуется T, чтобы его можно было копировать из 0. Это не так для многих классов. Вместо этого следует использовать std::declval для создания значения:

template <class O>
struct is_ostreamable {
    template <class T>
    static auto check(int) -> decltype(std::declval<std::ostream &>() << std::declval<T>(), std::true_type());
    template <class>
    static auto check(...) -> std::false_type;

public:
    static constexpr bool value{std::is_same_v<decltype(check<O>(0)), std::true_type>};
};

Здесь вносятся два изменения:

  • Операнд decltype использует std::declval<T>() для создать объект типа T. std::declval<T> - это (намеренно неопределенный) шаблон функции, который генерирует объект типа T при использовании в неоцененном операнде (например, в операторе decltype или sizeof, noexcept и т. Д. c. ) вне зависимости от заданной c сигнатуры конструкции (в вашем случае копирование-конструкция из 0).

  • Параметр check заменяется на int. Инициализатор переменной value вызывает check с аргументом 0, поэтому этот параметр int гарантирует, что (int) занимает место выше, чем (...) в разрешении перегрузки, так что перегрузка true_type выбирается при возможно.


Для манипуляторов функционального типа требуется специальная перегрузка (std::endl, std::flush, et c.):

using manip = std::ostream& (*)(std::ostream&);

CustomOutput& operator<<(manip m) {
    os << m;
    return *this;
}

К сожалению, нет никакого способа заставить шаблонную версию c поддерживать эту функцию. Это потому, что std::endl является шаблоном функции:

template <class CharT, class Traits>
std::basic_ostream<CharT, Traits>& endl(td::basic_ostream<CharT, Traits>& os);

Для использования шаблона функции должны быть определены соответствующие аргументы шаблона. Невозможно вывести параметр type-template T как шаблон generi c.

В любом случае, это, вероятно, единственная специальная перегрузка, которая вам понадобится.

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

Я знаю, что уже есть принятый ответ, но я хотел бы упомянуть немного более симпатичный способ сделать то же самое в C ++ 20, используя понятия:

#include <iostream>
#include <concepts>

using OManipulator= std::ostream&(&)(std::ostream &);

template <typename T>
concept OStreamable = requires(T t) {
    std::declval<std::ostream&>() << t;
};

struct CustomOutput {
    std::ostream &os;

    CustomOutput(std::ostream &os = std::cout)
        : os{os}
    {}

    template <typename T> requires OStreamable<T>
    CustomOutput& operator<<(T out) {
        os << out;
        return *this;
    }

    CustomOutput& operator<<(OManipulator out) {
        os << out;
        return *this;
    }
};

int main(void){
    CustomOutput{} << "hello";
    CustomOutput{} << std::endl;
    CustomOutput{} << "world";
}

В основном, в C ++ 20 Проблема с манипуляторами должна решаться так же, как и до C ++ 20, обеспечивая для них специальную перегрузку.

...