20

int x = fromString("test") :could not deduce template argument for 'ValueType'

int x = fromString<int>("test") : works fine as expected

So why does the compiler struggle here? I see it with all kinds of real template functions, not just this silly example. It must be a feature of the language, but what?

Mr. Boy
  • 60,845
  • 93
  • 320
  • 589
  • 12
    Why is something that can convert to an int called "toString"? – Marcelo Cantos Nov 17 '11 at 10:35
  • 2
    [This question](http://stackoverflow.com/questions/442026/function-overloading-by-return-type) discusses a similar problem. – Björn Pollex Nov 17 '11 at 10:38
  • It "just doesn't". Note that this is the language, not the compiler -- though really it's not specified in the language partially because it _would_ be tricky for the compiler to get right in the general case. – Lightness Races in Orbit Nov 17 '11 at 10:43
  • @MarceloCantos my mistake, fixed. – Mr. Boy Nov 17 '11 at 10:43
  • 4
    If it was allowed, the language would also need rules to resolve the obvious ambiguities that would arise. For example, `template T doubleit(T t) { return 2*t; }`. Now, does `int i = doubleit(0.5);` call `doubleit` (to match `i`) or `doubleit` (to match `0.5`)? The result is different, so even if the language had a rule to resolve the ambiguity, anyone reading the code could easily make a mistake. Letting the type and meaning of a sub-expression depend only on the sub-expression itself, not the surrounding expression, is at least simple. – Steve Jessop Nov 17 '11 at 11:58
  • @SteveJessop C++ already tries to deduce template arguments in other situations, and the compiler just gives an error when there is ambiguity. So I don't see a (theoretical) problem taking the same approach, it'd be consistent. – Mr. Boy Nov 17 '11 at 14:32

5 Answers5

26

You can't deduce based on the return type. You can, however, implement a workaround with similar syntax, using the overloaded cast operator:

#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;
}

Output:

42 1 0
Vlad
  • 18,195
  • 4
  • 41
  • 71
  • Universal conversion function, ouch! But since `operator=` can't be a `friend` I suppose this is the best solution, +1. – Potatoswatter Nov 17 '11 at 12:40
16

C++ doesn't do type inference on the return value. I.e., the fact that it is being assigned to an int isn't used in template parameter deduction.

(Removed edit, since someone else presented the overloaded cast solution already.)

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • 1
    Thanks. I don't _need_ it to, it's just one of those slightly irritating quirks and I wanted to understand why. – Mr. Boy Nov 17 '11 at 10:44
1

It looks like your template has the return type templated which cannot be automatically deduced which is why you need to add it in here.

Firedragon
  • 3,685
  • 3
  • 35
  • 75
  • Deferred return type declaration (or whatever it's called) has a different purpose. It allows you to declare the return type based on the type of the function's arguments. It just postpones declaring the return type until the arguments are in scope. It does not make it possible to deduce the type of the result from caller's usage. – visitor Nov 17 '11 at 11:01
  • Thanks for the comment, I will remove from my post :-) – Firedragon Nov 17 '11 at 11:13
1

Besides the bad choice for an example (probably makes sense to have int x = to<int>("1235") rather than toString), the problem is that the return type does not participate in overload resolution or type inference[1]. The reason for this is that the expression can be used in many places where the type of the return cannot be deduced:

// 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!

So the decision is that the return type will not take part in overload resolution or type deduction.

[1] There is a single exception to this rule, which is the evaluation of a function pointer with more than one overload, where the overload must be selected by either the destination pointer or an explicit cast, but this is just the one exception and is not used in any other context:

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
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
0

The return type of a function is dependent on overload resolution, not the other way around.

There is a trick that works though: operator= usually exists only for equal LHS/RHS argument types, except when an explicit operator= is defined (whether as standalone or as a member does not matter).

Thus, overload resolution will find operator=(int &, int), and see if the return value from your function is convertible to int. If you return a temporary that has an operator int, this is an acceptable resolution (even if the operator int is in the generic form of a template<typename T> operator T).

Thus:

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; }
Simon Richter
  • 28,572
  • 1
  • 42
  • 64