【问题标题】:Prevent instantiation of template class for types not supported by stringstream extraction operator (>>)防止为字符串流提取运算符 (>>) 不支持的类型实例化模板类
【发布时间】:2019-01-31 16:43:05
【问题描述】:

我正在尝试学习一些关于模板和元函数的知识,即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&lt;&gt; 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 &gt;&gt; 运算符支持的所有值。

【问题讨论】:

  • 为什么不能将字符串转换为向量?编写流运算符将向量转换为字符串并返回非常简单。如果未定义运算符,那么无论如何您都会收到编译器错误,因此 SFINAE 真的不会为您带来任何好处。
  • @NathanOliver 这就是我一直需要帮助的原因——如果流操作符被扩展,如果我的输入函数允许无缝扩展就好了。否则我也许可以手动过滤允许的类型,但这似乎是一个糟糕的解决方法。你是对的,错误使用它会导致编译器错误,但我想学习如何检测这些东西 - 也许使用 static_assert 来打印合适的错误,而不是 enable_if。
  • 好的。 static_asset 是与 SFINAE 不同的场景。 SFINAE 仍然可以给你可怕的错误信息。至少你有几个答案可以让你使用static_assert

标签: c++ templates sfinae stringstream enable-if


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    我认为您正在尝试将std::enable_if 用于不需要它的东西。如果您的模板函数已经依赖于应用在泛型类型T 上的operator&lt;&lt;,那么如果运算符不是专门针对该类型的,那么编译无论如何都会失败。

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

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

    【讨论】:

    • 这是有道理的,你可能是对的。在这种情况下,也许 static_assert 是一个更好的选择,这样我就可以发送一个不那么令人困惑的错误消息。但这仍然依赖于编译时断言,对吧?所以我还需要对输入类型进行某种验证?
    【解决方案3】:

    您可以按照此处on 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;
    }
    

    如果实现了运算符,则返回 1,否则返回 0。

    这样你就可以把你的 sn-p 改成

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

    【讨论】:

      【解决方案4】:

      你想要几样东西:

      • 一个特征,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&lt;T1&gt; 是否有效。

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

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

      【讨论】:

        猜你喜欢
        • 2013-05-05
        • 1970-01-01
        • 2017-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-26
        • 2016-01-24
        • 1970-01-01
        相关资源
        最近更新 更多