Я пытаюсь немного узнать о шаблонах и метафункциях, а именно о 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<> в случаях, когда преобразование невозможно, скажем:
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 >>.
@NathanOliver Это своего рода причина, по которой мне нужна помощь - если оператор потока расширен, было бы неплохо, если бы моя функция ввода позволяла расширение без проблем. В противном случае я мог бы отфильтровать разрешенные типы вручную, но это кажется плохим обходным путем. Вы правы, что из-за неправильного использования возникнет ошибка компилятора, но я хочу научиться обнаруживать такие вещи - возможно, с помощью static_assert, чтобы вывести подходящую ошибку, а не enable_if.
В ПОРЯДКЕ. static_asset — это другой сценарий, чем SFINAE. SFINAE по-прежнему может выдавать ужасные сообщения об ошибках. По крайней мере, у вас есть пара ответов, которые позволят вам использовать static_assert.





Если вы предпочитаете использовать SFINAE с параметром шаблона класса, вам нужно
template <
typename T,
typename = decltype(std::declval<std::istringstream &>() >> std::declval<T &>(), void())
>
class Input /*...*/
Я думаю, что вы пытаетесь использовать std::enable_if для чего-то, что не требует этого. Если ваша шаблонная функция уже использует operator<<, примененный к общему типу T, то компиляция в любом случае завершится ошибкой, если оператор не специализирован для этого типа.
Ничто не мешает вам использовать std::enable_if для решения вашей конкретной проблемы, хотя это может быть не лучший способ сделать это.
Если бы С++ 20 понятия уже был широко принят, я бы сказал, что это был бы ваш путь.
Это имеет смысл, и вы, вероятно, правы. Возможно, в этом случае static_assert будет лучшим выбором, просто чтобы я мог отправить менее запутанное сообщение об ошибке. Но это по-прежнему зависит от утверждения времени компиляции, верно? Итак, мне все еще нужно выполнить какую-то проверку типа ввода?
Вы можете пойти по пути, предложенному здесь на ТАК, и реализовать класс 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>
Есть несколько вещей, которые вы хотите:
Для трейтов вы можете использовать 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 в большинстве случаев не обязательно.
Почему нельзя преобразовать строку в вектор? Довольно тривиально написать операторы потока для преобразования вектора в строку и обратно. Если оператор не определен, вы все равно получите ошибку компилятора, поэтому SFINAE ничего вам не даст.