防止为字符串流提取运算符 (>>) 不支持的类型实例化模板 class

Prevent instantiation of template class for types not supported by stringstream extraction operator (>>)

我正在尝试学习一些关于模板和元函数的知识,即 std::enable_if。我正在为我们的学校作业(请注意,课外活动)制作一个菜单系统,并且需要一种从用户那里获取输入的方法。我想为各种类型的输入定义一个模板 class - 按照以下方式使用:

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 子句格式化为仅允许为上面列出的类型实例化我的模板 class?或者,有没有更好的方法来解决这个问题?我是不是完全搞错了?

到目前为止我做了什么

我相信我已经找到了一个允许的类型列表,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 >> 运算符支持的所有值。

如果您更喜欢使用带有 class 模板参数的 SFINAE,那么您需要

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

我认为您正试图将 std::enable_if 用于不需要它的东西。如果您的模板函数已经依赖于应用于通用类型 Toperator<<,那么如果运算符不是针对该类型专门化的,则无论如何编译都会失败。

没有什么能阻止您使用 std::enable_if 来解决您的特定问题,尽管这可能不是最好的方法。

如果 C++20 concepts 已被广泛采用,我会说那将是您的选择。

您可以按照此处建议的方式进行 on SO 并实施 class 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;
}

如果运算符已实现,这将 return 为 1,否则为 0。

有了它,您可以将代码段更改为

template <typename T, typename = typename 
std::enable_if<is_streamable<std::stringstream, C>::value, T>::type>

有几个你想要的东西:

  • 一个特质,is_streamable
  • 一种禁止class实例化的方法。

对于特征,您可以使用 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 友好 class:

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

所以你可以知道 Input<T1> 是否有效。

请注意,如果没有所有这些东西,您的程序在实例化有问题的方法时无论如何都不会编译(硬错误,所以 SFINAE 不友好)。

大多数时候不需要对 SFINAE 友好。