【问题标题】:Argument type in templated function forced to a given type?模板函数中的参数类型强制为给定类型?
【发布时间】:2021-10-30 09:08:50
【问题描述】:

以下运行正常:

#include <iostream>
#include <string>

/**
 * Simply print the value of the argument and adapt the 
 * statement based on its type. 
 */
template<typename T>
int function(T value){

    if(std::is_same<T,double>() || std::is_same<T,float>()){
        std::cout << value << ", type is float" << std::endl; 
        return 0; 
    }
    else if(std::is_same<T,std::string>()){
        std::cout << value << ", type is string" << std::endl; 
        return 0; 
    }
    else if(std::is_same<T,char*>() || std::is_same<T,const char*>()){
        std::cout << value << ", type is char*" << std::endl; 
        return 0; 
    }
    else{
        std::cout << "Unsupported data type" << std::endl; 
        return -1; 
    }
}

int main(){
    function<double>(1.2345); 
    function<std::string>("Hello World");
    const char* sentence = "Hello World"; function<const char*>(sentence);
    return 0; 
}

由于运行没有任何问题,我尝试更改以下行:

std::cout << value << ", type is string" << std::endl;

std::cout << value.c_str() << ", type is string" << std::endl;

但是这不会编译。就像在编译 .c_str() 调用时定义了 value 的类型一样。但是,我预计value 参数的类型仅取决于模板参数T。为什么在这种情况下不编译?

产生以下错误:

In instantiation of ‘int function(T) [with T = double]’:
.cpp:321:25:   required from here
error: request for member ‘c_str’ in ‘value’, which is of non-class type ‘double’
   std::cout << value.c_str() << ", type is string" << std::endl;
                ~~~~~~^~~~~
In instantiation of ‘int function(T) [with T = const char*]’:
.cpp:323:70:   required from here
.cpp:306:22: error: request for member ‘c_str’ in ‘value’, which is of non-class type ‘const char*’

【问题讨论】:

  • 为什么要这样?你传递了const char*,它没有.c_str() 函数。你也通过了double,这同样不符合这个要求
  • 您可能正在寻找if constexpr。 “正常”if 在运行时检查其状况;所有分支都必须编译,即使实际上只使用了一个。
  • 为了完整起见,std::stringconst char* 都应该被处理。问题出在std::string 案例上。
  • @Lala5th,您能详细说明一下吗?我的问题是为什么它不应该编译?我错过了什么?
  • @LoW Igor 在他们的评论中很好地解释了这一点

标签: c++ c++11 templates


【解决方案1】:

在您的原始代码中,您实际上并没有使用value 的任何成员,因此它的类型与if 语句的主体完全无关。您只是依靠operator&lt;&lt; 重载来“做正确的事”。

但是,一旦您开始访问value 的成员,它的类型就会变得与您的代码非常相关。

例如,让我们以您的第一个测试用例double为例。编译器不会实例化您期望的函数版本:

int function<double>(double value){
    std::cout << value << ", type is float" << std::endl; 
    return 0; 
}

它将 INSTEAD 实例化这个版本的函数:

int function<double>(double value){

    if(std::is_same<double,double>() || std::is_same<double,float>()){ //true
        std::cout << value << ", type is float" << std::endl; 
        return 0; 
    }
    else if(std::is_same<double,std::string>()){ // false
        std::cout << value.c_str() << ", type is string" << std::endl;
        return 0; 
    }
    else if(std::is_same<double,char*>() || std::is_same<double,const char*>()){ // false
        std::cout << value << ", type is char*" << std::endl; 
        return 0; 
    }
    else{
        std::cout << "Unsupported data type" << std::endl; 
        return -1; 
    }
}

您的std::stringconst char* 测试用例也将实例化相似的版本。不是您所期望的。

这是因为您的函数在运行时创建 std::is_same 的实例并调用其 bool 转换运算符,因此所有这些 ifs 都是运行时检查,因此它们的分支必须包含在编译时要在运行时执行的有效代码。在编译时无法消除任何分支。由于doubleconst char* 没有c_str() 成员,因此您会看到所看到的错误。

现在,您可能想通过将std::is_same&lt;T,...&gt;() 替换为std::is_same&lt;T,...&gt;::valuestd::is_same_v&lt;T,...&gt;(它们是编译时常量)来避免这种运行时行为。你这样做是对的:

template<typename T>
int function(T value){

    if(std::is_same_v<T,double> || std::is_same_v<T,float>){
    //if(std::is_same<T,double>::value || std::is_same<T,float>::value){
        std::cout << value << ", type is float" << std::endl; 
        return 0; 
    }
    else if(std::is_same_v<T,std::string>){
    //else if(std::is_same<T,std::string>::value){
        std::cout << value.c_str() << ", type is string" << std::endl;
        return 0; 
    }
    else if(std::is_same_v<T,char*> || std::is_same_v<double,const char*>){
    //else if(std::is_same<T,char*>::value || std::is_same<double,const char*>::value){
        std::cout << value << ", type is char*" << std::endl; 
        return 0; 
    }
    else{
        std::cout << "Unsupported data type" << std::endl; 
        return -1; 
    }
}

这将简化为,例如double

int function<double>(double value){

    if(true){
        std::cout << value << ", type is float" << std::endl; 
        return 0; 
    }
    else if(false){
        std::cout << value.c_str() << ", type is string" << std::endl;
        return 0; 
    }
    else if(false){
        std::cout << value << ", type is char*" << std::endl; 
        return 0; 
    }
    else{
        std::cout << "Unsupported data type" << std::endl; 
        return -1; 
    }
}

但是,编译器仍然需要完全评估每个 if 分支内的代码,因此每个分支仍然需要包含有效代码,即使编译器能够优化评估其代码之后的任何未使用的分支。在这种情况下,value.c_str() 对于没有 c_str() 成员的 T 类型仍然是无效代码。

要解决这个问题,您需要使用if constexpr,它将执行编译时检查在评估其代码之前消除未使用的分支,例如:

template<typename T>
int function(T value){

    if constexpr (std::is_same_v<T,double> || std::is_same_v<T,float>()){
        std::cout << value << ", type is float" << std::endl; 
        return 0; 
    }
    else if constexpr (std::is_same_v<T,std::string>){
        std::cout << value.c_str() << ", type is string" << std::endl; 
        return 0; 
    }
    else if constexpr (std::is_same_v<T,char*> || std::is_same_v<T,const char*>){
        std::cout << value << ", type is char*" << std::endl; 
        return 0; 
    }
    else{
        std::cout << "Unsupported data type" << std::endl; 
        return -1; 
    }
}

现在,编译器将能够按照您的预期实例化您的函数:

int function<double>(double value){ // and float
    std::cout << value << ", type is float" << std::endl; 
    return 0; 
}

int function<std::string>(std::string value){
    std::cout << value.c_str() << ", type is string" << std::endl; 
    return 0; 
}

int function<const char*>(const char* value){
    std::cout << value << ", type is char*" << std::endl; 
    return 0; 
}

template<typename T>
int function(T value){
    std::cout << "Unsupported data type" << std::endl; 
    return -1; 
}

【讨论】:

    【解决方案2】:

    感谢@Igor Tandetnik 建议使用constexpr@Lala5th 强调他的建议。

    函数应该如下所示:

    template<typename T>
    int function(T value){
        if constexpr (std::is_same<T,double>() || std::is_same<T,float>()){
            std::cout << value << ", type is float" << std::endl; 
            return 0; 
        }
        else if constexpr (std::is_same<T,std::string>()){
            std::cout << value.c_str() << ", type is string" << std::endl; 
            return 0; 
        }
        else if constexpr (std::is_same<T,char*>() || std::is_same<T,const char*>()){
            std::cout << value << ", type is char*" << std::endl; 
            return 0; 
        }
        else{
            std::cout << "Unsupported data type" << std::endl; 
            return -1; 
        }
    }
    

    【讨论】:

    • 你真的应该使用std::is_same&lt;T,...&gt;::value,或者更好的std::is_same_v&lt;T,...&gt;,而不是std::is_same&lt;T,double&gt;()。后者创建std::is_same 的对象实例并调用其bool 转换运算符,这是不必要的,因为value 成员是编译时常量
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多