Class Template Argument Deduction for template dependent parameter

308 views Asked by At

Let's start with a simple add method for class number:

class number {
    int num;
public:
    number(int num = 0): num(num) {}
    operator int() const { return num; }
};

number add(number t1, number t2) {
    return t1 + t2;
}

int main() {
    auto result1 = add(1, 2); // auto-casting, works fine
}

Now we want to turn number into a template class:

template<class T>
class number {
    T num;
public:
    number(T num = 0): num(num) {}
    operator T() const { return num; }
};

template<class T>
number<T> add(number<T> t1, number<T> t2) {
    return t1 + t2;
}

Trying to call add the same as we called the simple non-templated one, based (theoretically!) on CTAD:

int main() {
    number a = 3; // works, using CTAD
    // auto result1 = add(1, 2); // <== what we wish for, but can't find method
    // auto result2 = add(a, 2); // this also doesn't work, no ADL here :(
    auto result3 = add<int>(1, 2); // this works, but is not what we wish
}

Note that if add was a friend function, calling it with one of the parameters being a number would work, based on ADL:

template<class T>
class number {
    T num;
public:
    number(T num = 0): num(num) {}
    operator T() const { return num; }
    friend number add(number t1, number t2) {
        return t1 + t2;
    }
};

int main() {
    number a = 3; // works, using CTAD
    auto result1 = add(a, 2); // works, based on ADL
    // auto result2 = add(1, 2); // still doesn't work, no ADL here :(
}

Any suggestion how to allow the template class to behave similarly to the non-template, having auto casting when calling add?


EDIT: This question was edited based on posted comments. It should be emphasized that for a generic function like add having such an auto casting might be a wrong idea, but suppose that the method is very specific, something like doSomethingWithNumbers.

2

There are 2 answers

2
Yehezkel B. On BEST ANSWER

I think the answer is simple. In the first case, with the free function template, what kicks in first is overloading resolution and function template argument deduction. As the compiler has no way to detect T from the int passed as argument (clang says: candidate template ignored: could not match 'number<type-parameter-0-0>' against 'int'), the overloading resolution fails and the program is ill formed.

When the function is defined as friend, it's a non-template function. The compiler created it when instantiating the class (for number<int>; first line in main). Now, when it finds it (using ADL), the parameter types are already set (both are number<int> because it comes from number<int> instantiation), and what's left is to decide on how to convert the passed argument from int to number<int>, where the implicit conversion (by the matching c-tor) is used. No CTAD here either.

A similar (but not exactly the same) case is discussed by Scott Meyers in Effective C++ (3rd edition), Item 46: Define non-member functions inside templates when type conversions are desired.

Edit: So to answer the question, function template argument deduction and implicit type conversion for the arguments can't be mixed. Choose one. (Which is what Meyers explains in the mentioned item.)

0
Amir Kirsh On

In the line of a comment by @Taekahn, we can achieve the desired behavior, though it is not an automatic casting:

// the additional `requires` on number is not mandatory for the example
// but it is reasonable that number would be restricted
template<class T> requires std::integral<T> || std::floating_point<T>
class number {
    T num;
public:
    number(T num = 0): num(num) {}
    operator T() const { return num; }
};

template<typename T>
concept Number = requires(T t) {
    number(std::move(t)); // anything that a number can be created from
                          // that includes number itself
};

auto add(Number auto t1, Number auto t2) {
    return number{std::move(t1)} + number{std::move(t2)};
}

int main() {
    number a = 3;
    auto result1 = add(1, 2);
    auto result2 = add(a, 2);
    auto result3 = add<double>(1, 2);
}

Code: https://godbolt.org/z/_nxmeR