为什么C++不能从赋值中推断模板类型?

20

int x = fromString("test")无法推导出 'ValueType' 的模板参数

int x = fromString<int>("test") :按预期正常工作。

那么编译器为什么会有问题呢?我在各种真实的模板函数中都遇到了这个问题,而不仅仅是这个愚蠢的例子。这一定是语言的一个特性,但是它是什么呢?


12
为什么能够转换成整数的东西被称为 "toString" ? - Marcelo Cantos
2
这个问题讨论了一个类似的问题。 - Björn Pollex
它“就是不行”。请注意,这是语言问题,而不是编译器问题——尽管实际上在语言中没有明确指定,部分原因是在一般情况下编译器很难正确处理。 - Lightness Races in Orbit
@MarceloCantos 我的错误,已修复。 - Mr. Boy
4
如果允许的话,该语言也需要规则来解决显然会出现的歧义。例如,template <typename T> T doubleit(T t) { return 2*t; }。现在,int i = doubleit(0.5);是否调用doubleit<int>(以匹配i)或者doubleit<double>(以匹配0.5)?结果是不同的,因此即使该语言有解决歧义的规则,读取代码的任何人都很容易犯错。让子表达式的类型和含义仅依赖于子表达式本身,而不依赖于周围的表达式,至少是简单的。 - Steve Jessop
@SteveJessop C++ 已经尝试在其他情况下推断模板参数,当存在歧义时编译器会产生错误。因此,我认为采用同样的方法并没有(理论上的)问题,这将是一致的。 - Mr. Boy
5个回答

26

你不能根据返回类型进行推断。但是,你可以使用重载的强制类型转换运算符实现类似语法的解决方法:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

class FromString{
private:
    string m_data;
public:
    FromString(const char*data) : m_data(data) {} 

    template<typename T>
    operator T(){
        T t;
        stringstream ss(m_data);
        ss >> t;
        return t;
    }

};

template<> FromString::operator bool(){
    return (m_data!="false"); //stupid example
}

int main(){

    int ans = FromString("42");    
    bool t = FromString("true");
    bool f = FromString("false");

    cout << ans << " " << t << " " << f << endl;

    return 0;
}

输出:

42 1 0

通用转换函数,哎呀!但是由于operator=不能是friend,我想这是最好的解决方案,+1。 - Potatoswatter

16

C++不会对返回值进行类型推断,也就是说,分配给int类型并不会在模板参数推导中使用。

(已删除编辑,因为其他人已经提供了过载强制转换的解决方案。)


1
谢谢。我不“需要”它这样做,只是因为它是那些稍微有点烦人的怪癖之一,我想了解其中的原因。 - Mr. Boy

1

看起来你的模板有模板化的返回类型,这种类型无法自动推断,所以你需要在这里添加它。


延迟返回类型声明(或类似称呼)具有不同的用途。它允许您根据函数参数的类型声明返回类型。它只是推迟了在参数可见之前声明返回类型。这并不能使得从调用者的使用中推断结果类型成为可能。 - visitor
谢谢您的评论,我会从我的帖子中删除 :-) - Firedragon

1
除了选择不当的示例(可能更合理的是使用int x = to<int>("1235")而不是toString),问题在于返回类型不参与重载解析或类型推断[1]。原因是该表达式可以用于许多无法推断返回类型的地方:

// assuming template <typename T> T to( std::string ):
//
f( to("123") );          // where there are two overloads f(int), f(double)
int x = 1.5 * to("123"); // T == int? T == double?
to("123");               // now what? returned object can be ignored!

因此,决定是返回类型不参与重载解析或类型推导。

[1] 这个规则有一个例外,即对具有多个重载的函数指针进行评估时,必须通过目标指针或显式转换来选择重载,但这只是一个例外,在任何其他情况下都不使用:

void f();
void f(int);
void g( void (*)() );
void g( void (*)(int) );

void (*p1)() = &f;      // overload selected based on destination type
void (*p2)(int) = &f;
g( (void (*)(int))&f ); // overload selected based on explicit cast

0

函数的返回类型取决于重载决议,而不是相反。

然而有一个技巧可行:通常只有等式左右两边参数类型相等时才存在operator=,除非定义了显式的operator=(无论是作为独立函数还是成员函数都可以)。

因此,重载决议将找到operator=(int &, int),并查看您的函数返回值是否可转换为int。如果您返回一个具有operator int的临时对象,则这是一种可接受的解决方案(即使operator inttemplate<typename T> operator T的通用形式出现)。

因此:

template<typename T, typename U>
U convert_impl(T const &t);

template<typename T>
struct convert_result {
    convert_result(T const &t) : t(t) { }
    template<typename U> operator U(void) const { return convert_impl<U>(t); }
    T const &t;
};

template<typename T>
convert_result<T> convert(T const &t) { return t; }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接