C ++模板函数无法将“ std:string”转换为“ double”作为回报

时间:2019-11-26 14:28:28

标签: c++ types compilation

我有一个从环境变量中获取数据的功能。当我提到数字类型时,它工作正常,但是当我提到值应为字符串类型时,则在编译时会出错:

g.cc: In instantiation of ‘T result(const char *, const T&) [with T = double; std::string = std::__cxx11::basic_string<char>]’:
g.cc:65:88:   required from here
g.cc:23:33: error: cannot convert ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’} to ‘double’ in return
   23 |       return std::string(env_val);
      |                            
#include <iostream>
#include <cstdlib>
#include <type_traits>

template <typename T>
T result(const char *key, const T &default_value) {
    char *env_val = getenv(key);
    if (std::is_integral<T>::value)
        return atoi(env_val);
    else if(std::is_floating_point<T>::value)
        return atof(env_val);
    else if(std::is_base_of<std::string<char>, T>::value)
        return std::string(env_val);
    return default_value;
}

int main() {
    result<int>("test1", 12);          // Ok
    result<double>("test2", 12.2);       // Ok
    result<std::string>("test3", "test3");  // ERROR
}

我已经提到返回T作为结果,但是它显示了此错误。

4 个答案:

答案 0 :(得分:1)

您的if语句

    if (std::is_integral<T>::value)
        return atoi(env_val);
    else if(std::is_floating_point<T>::value)
        return atof(env_val);
    else if(std::is_base_of<std::string<char>, T>::value)
        return std::string(env_val);

并不是仅仅因为其条件是错误的就神奇地消失了。当使用T = double实例化此模板时,实际上是告诉编译器编译以下代码:

double result(const char *key, const double &default_value) {
    char *env_val = getenv(key);
    if (std::is_integral<double>::value)
        return atoi(env_val);
    else if(std::is_floating_point<double>::value)
        return atof(env_val);
    else if(std::is_base_of<std::string, double>::value)
        return std::string(env_val);
    return default_value;
}

请注意所有return语句如何仍然存在...

从C ++ 17开始,您可以使用if constexpr来使不想要的代码消失:

template <typename T>
T result(const char *key, const T &default_value) {
    char *env_val = getenv(key);
    if constexpr (std::is_integral_v<T>)
        return atoi(env_val);
    else if constexpr (std::is_floating_point_v<T>)
        return atof(env_val);
    else if constexpr (std::is_base_of_v<std::string, T>)
        return std::string(env_val);
    return default_value;
}

或者,您也可以使用SFINAE,例如:

template <typename T>
auto result(const char* key, const T& default_value) -> std::enable_if_t<std::is_integral_v<T>, T> {
    return std::atoi(getenv(key));
}

template <typename T>
auto result(const char* key, const T& default_value) -> std::enable_if_t<std::is_floating_point_v<T>, T> {
    return std::atof(getenv(key));
}

template <typename T>
auto result(const char* key, const T& default_value) -> std::enable_if_t<std::is_base_of_v<std::string, T>, T> {
    return {getenv(key)};
}

除了所有这些,我建议重新考虑您是否真的想要为从字符串派生的类提供特殊行为的最新版本。在我看来,这似乎是一个非常脆弱的技巧,以实现应该以根本不同的方式完成的工作。最值得注意的是,它不适用于T = std::string

答案 1 :(得分:1)

这是因为函数中的每个分支都是无条件编译的。即使这些是if中的常量,编译器也必须编译所有分支。每个分支都有不同的返回类型,因此会出现错误。

您想改用if constexpr,它告诉编译器将每个分支视为单独的模板,仅当条件为true时才应实例化。

template <typename T>
T result(const char *key, const T &default_value) {
    char *env_val = getenv(key);
    if constexpr(std::is_integral<T>::value)
        return atoi(env_val);
    else if constexpr(std::is_floating_point<T>::value)
        return atof(env_val);
    else if constexpr(std::is_base_of<std::basic_string<char>, T>::value)
        return std::string(env_val);
    return default_value;
}

答案 2 :(得分:1)

对于if语句,您需要使用constexpr,否则编译器将尝试编译每个可能的路由。

T result(const char *key, const T &default_value ) {
    char *env_val = getenv(key);
    if constexpr (std::is_integral<T>::value)
        return atoi(env_val);
    else if constexpr (std::is_floating_point<T>::value)
        return atof(env_val);
    else if constexpr (std::is_base_of<std::string, T>::value)
        return std::string(env_val);
    return default_value;
}

答案 3 :(得分:1)

我将以完全不同的方式解决这个问题:

template<typename T>
bool convert_to(std::string_view s, T& result)
{
    std::istringstream input;
    input.rdbuf()->pubsetbuf(const_cast<char *>(s.data()), s.length());
    return input >> result >> std::ws && input.eof();
}

bool convert_to(std::string_view s, std::string& result)
{
    result = s;
    return true;
}

template<typename T>
std::optional<T> get_env(std::string_view name)
{
    auto r = getenv(name.data());
    if (!r) return {};
    T result;
    if (!convert_to(r, result)) return {};
    return result;
}

template<typename T>
T get_env_or(std::string_view name, T&& defValue)
{
    return get_env<T>(name).value_or(std::forward<T>(defValue));
}

Live demo

优点:如果剥离string_viewoptional(在本例中为糖衣),则几乎可以使用任何C ++版本。