Запретить создание экземпляра класса шаблона для типов, не поддерживаемых оператором извлечения строкового потока (››)

Я пытаюсь немного узнать о шаблонах и метафункциях, а именно о std::enable_if. Я делаю систему меню для наших школьных заданий (внеклассных, заметьте), и мне нужен способ получения информации от пользователя. Я хотел бы определить класс шаблона для различных типов ввода - что-нибудь, используемое в следующих строках:

std::string userInput = Input<std::string>("What's your name?").Show();
float userHeight = Input<float>("How tall are you?").Show();

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

Достаточно легко увидеть, не удалось ли преобразование во время выполнения, но я бы хотел использовать std::enable_if, чтобы люди не могли использовать мой Input<> class в случаях, когда преобразование невозможно, например:

std::vector<Boats> = Input<std::vector<>>("Example").Show();

Очевидно, что std::stringstream не может преобразовать строку в вектор, поэтому всегда будет терпеть неудачу.

У меня такой вопрос:

Могу ли я отформатировать предложение std::enable_if, чтобы разрешить создание экземпляра моего класса шаблона ТОЛЬКО для перечисленных выше типов? В качестве альтернативы, есть ли лучший способ сделать это? У меня все совсем не так?

Что я сделал до сих пор

Я считаю, что нашел список разрешенных типов, которые std::stringstream могут преобразовывать строку в:

http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/

Я использовал std::enable_if вот так до этого момента:

template <typename T, typename = typename 
std::enable_if<std::is_arithmetic<T>::value, T>::type>

Однако теперь я хотел бы расширить его, чтобы разрешить не только арифметические значения, но и все значения, поддерживаемые оператором sstream >>.


person Olaxan    schedule 31.01.2019    source источник
comment
Почему нельзя преобразовать строку в вектор? Довольно тривиально написать операторы потока для преобразования вектора в строку и обратно. Если оператор не определен, вы все равно получите ошибку компилятора, поэтому SFINAE ничего вам не даст.   -  person NathanOliver    schedule 31.01.2019
comment
@NathanOliver Это одна из причин, по которой мне нужна помощь - если оператор потока расширен, было бы неплохо, если бы моя функция ввода позволяла расширение без проблем. В противном случае я, возможно, мог бы отфильтровать разрешенные типы вручную, но это похоже на плохой обходной путь. Вы правы в том, что ошибка компилятора возникнет из-за неправильного его использования, но я хочу узнать, как обнаруживать эти вещи - возможно, с помощью static_assert, чтобы вывести подходящую ошибку, а не enable_if.   -  person Olaxan    schedule 31.01.2019
comment
В ПОРЯДКЕ. static_asset - это другой сценарий, чем SFINAE. SFINAE по-прежнему может выдавать ужасные сообщения об ошибках. По крайней мере, у вас есть пара ответов, которые позволят вам использовать static_assert   -  person NathanOliver    schedule 31.01.2019


Ответы (4)


Если вы предпочитаете использовать SFINAE с параметром шаблона класса, тогда вам нужно

template <
    typename T,
    typename = decltype(std::declval<std::istringstream &>() >> std::declval<T &>(), void())
>
class Input /*...*/
person HolyBlackCat    schedule 31.01.2019

Я думаю, что вы пытаетесь использовать std::enable_if для чего-то, чего он не требует. Если ваша функция шаблона уже полагается на operator<<, примененный к универсальному типу T, то компиляция завершится неудачно в любом случае, если оператор не специализируется на этом типе.

Ничто не мешает вам использовать std::enable_if для решения вашей конкретной проблемы, хотя это может быть не лучший способ сделать это.

Если бы концепции C ++ 20 уже были широко приняты, я бы сказал, что будет ваш путь пойти.

person Jack    schedule 31.01.2019
comment
В этом есть смысл, и вы, вероятно, правы. Возможно, в этом случае static_assert будет лучшим выбором, просто чтобы я мог отправить менее запутанное сообщение об ошибке. Но это все еще зависит от утверждения времени компиляции, верно? Значит, мне все еще нужно выполнить какую-то проверку типа ввода? - person Olaxan; 31.01.2019

Вы можете пойти по пути, предложенному здесь на SO, и реализовать класс is_streamable, который может проверить это следующим образом:

#include <type_traits>
#include <utility>
#include <iostream>
#include <sstream>

template<typename S, typename T>
class is_streamable
{
    template<typename SS, typename TT>
    static auto test(int)
        -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type());

    template<typename, typename>
    static auto test(...)->std::false_type;

public:
    static const bool value = decltype(test<S, T>(0))::value;
};

class C
{
public:
    friend std::stringstream& operator<<(std::stringstream &out, const C& c);
};

std::stringstream& operator<<(std::stringstream& out, const C& c)
{
    return out;
}


int main() {
    std::cout << is_streamable<std::stringstream, C>::value << std::endl;
    return 0;
}

Это вернет единицу, если оператор реализован, и ноль, если нет.

С этим вы можете изменить свой фрагмент на

template <typename T, typename = typename 
std::enable_if<is_streamable<std::stringstream, C>::value, T>::type>
person user32434999    schedule 31.01.2019

Вам нужно несколько вещей:

  • черта, is_streamable
  • способ запретить создание экземпляров класса.

Для трейтов вы можете использовать std::experimental_is_detected или запустить свой собственный:

template <typename T>
auto is_streamable_impl(int)
-> decltype (T{},
             void(), // Handle evil operator ,
             std::declval<std::istringstream &>() >> std::declval<T&>(),
             void(), // Handle evil operator ,
             std::true_type{});

template <typename T>
std::false_type is_streamable_impl(...); // fallback, ... has less priority than int

template <typename T>
using is_streamable = decltype(is_streamable_impl<T>(0));

Затем, чтобы запретить интантию, несколько вариантов:

static_assert:

template <typename T>
class Input
{
    static_assert(is_streamable<T>::value);
    // ...
};

или дружественный класс SFINAE:

template <typename T, typename = std::enable_if_t<is_streamable<T>>>
class Input
{
    // ...
};

поэтому вы позволяете узнать, действительно ли Input<T1>.

Обратите внимание, что без всего этого ваша программа все равно не будет компилироваться при создании экземпляра проблемного метода (серьезная ошибка, поэтому SFINAE не подходит).

В большинстве случаев дружелюбие к SFINAE необязательно.

person Jarod42    schedule 31.01.2019