【问题标题】:How do i specialize my template for std::string我如何为 std::string 专门化我的模板
【发布时间】:2019-04-26 00:19:41
【问题描述】:

我有以下模板函数

template <typename As, typename std::enable_if<
std::is_arithmetic<As>::value, As>::type* = nullptr   > 
As getStringAs(const std::string& arg_name)
{
    std::istringstream istr(arg_name);
    As val;
    istr >> val;
    if (istr.fail())
        throw std::invalid_argument(arg_name);
    return val;
}

我想这样使用它:

getStringAs<float>("2.f");

什么是专门化std::string 的函数以便我可以编写的好方法

getStringAs<std::string>("2.f");

我已经尝试了所有已知的方法,但由于std::enable_if 的默认类型产生的歧义,它们似乎都失败了。 例如:如果我写:

template<>
std::string getStringAs<std::string>(const std::string& arg_name)
{    
}

这将不匹配任何模板重载。如果我添加第二种类型,这将产生歧义错误。我已经尝试过 google-in,但我唯一能找到的就是标签调度,但这会使用户端的调用变得丑陋。我正在考虑使用宏定义将getStringAs&lt;std::string&gt; 替换为调度标记的非常丑陋的解决方案。

谢谢!

【问题讨论】:

  • 你为什么有is_arithmetic支票?
  • 如果这是getStringAs的完整定义,那么就没有必要了。如果您有一个重载&gt;&gt; 的类,并且您希望将字符串作为该类获取?如果您删除 SFINAE,那么这将起作用。
  • 如果您想确保只能使用算术类型(而不是具有重载运算符的类),那么您可以在函数体中添加 static_assert 并获得更好的错误消息。跨度>

标签: c++ c++11 templates sfinae template-specialization


【解决方案1】:

我通常使用函数重载来解决这些问题。它允许您轻松地为更多类型扩展功能,并且您只需要在必要时使用 SFINAE(例如,std::is_arithmetic)。由于您不能通过返回类型重载,因此仅当您将结果存储在其中一个参数中时才有效。

template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type
getStringAsImpl(std::string const& in, T& out)
{
    std::istringstream sstr(in);
    sstr >> out;
    if (sstr.fail())
    {
        throw std::invalid_argument(in);
    }
}

void getStringAsImpl(std::string const& in, std::string& out)
{
    out = in;
}

template <typename T>
T getStringAs(std::string const& in)
{
    T out;
    getStringAsImpl(in, out);
    return out;
}

【讨论】:

    【解决方案2】:

    一种方法是将 SFINAE 移动到返回类型:

    template <typename As>
    auto getStringAs(const std::string& arg_name)
        -> typename std::enable_if<std::is_arithmetic<As>::value, As>::type;
    
    template <typename As>
    auto getStringAs(const std::string& arg_name)
        -> typename std::enable_if<std::is_same<As, std::string>::value, As>::type;
    

    【讨论】:

    • 其实OP可以用模板参数做同样的事情。诀窍是使用重载而不是专业化。
    【解决方案3】:

    但由于 enable_if 的默认类型产生的歧义,它们似乎都失败了

    问题是如果你写

    template <> 
    std::string getStringAs<std::string> (const std::string& arg_name)
     { return arg_name; }
    

    特化与主模板不匹配,因为std::is_arithmetic&lt;std::string&gt;::value 为假,因此第二个模板参数未启用。

    一个可能的解决方案(我更喜欢bolov建议的解决方案,但只是为了探索其他方式并更好地理解问题)是使用std::string启用第二个模板参数,如下所示

    template <typename As, typename std::enable_if<
          std::is_arithmetic<As>::value 
       || std::is_same<As, std::string>::value, bool>::type = true> 
    As getStringAs(const std::string& arg_name)
    {
        std::istringstream istr(arg_name);
        As val;
        istr >> val;
        if (istr.fail())
            throw std::invalid_argument(arg_name);
    
        return val;
    }
    

    现在你可以像往常一样完全专精了

    template <> 
    std::string getStringAs<std::string> (const std::string& arg_name)
     { return arg_name; }
    

    观察std::string 现在匹配两个getStringAs() 版本,但编译器选择第二个因为更专业。

    【讨论】:

      最近更新 更多