类模板参数推导(Class Template Argument Deduction)用于依赖于模板的参数。

3

我们从一个简单的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
}

现在我们想把number变成一个模板类:

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

基于理论上的CTAD,我们尝试调用与简单非模板化版本相同的add方法:

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
}

请注意,如果add是一个友元函数,那么只要其中一个参数是number,根据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 :(
}

有什么建议可以让模板类在调用 add 函数时自动转换类型,从而与非模板类类似?注意:对于像 add 这样的通用函数具有这样的自动转换可能是个错误的想法,但假设该方法非常具体,例如 doSomethingWithNumbers。

5
这里的问题不太清楚。为什么add(1.0, 2.0)会调用一个与1.02.0类型完全不相关联的函数呢?编译器为什么会想到查找number命名空间中的add函数作为一种可能性,特别是它在另一个命名空间中? - Nicol Bolas
3
另外,不清楚这与CTAD有什么关系,因为此代码中没有使用CTAD。而且,add的定义不算在内。在模板定义内部,模板名称不带模板参数也作为当前实例化模板的名称。 - Nicol Bolas
add(number{1}, number{2}) 可以正常工作,并且它使用了 CTAD。 - bolov
@bolov 是的,但问题是如何避免显式创建所需类型,即实现与非模板“number”类相同的行为。 - Amir Kirsh
Amir,我个人建议不要使用隐式,但既然你似乎想要它,我建议使用类似于https://godbolt.org/z/RBXyzE的东西。 - Taekahn
@Taekahn 这个选项在之前版本的问题中出现过,编辑后被遗漏了。它确实可以完成工作。一个人还可以在模板参数类型上添加requires以将其限制在正确的范围内。看起来这是唯一实现这一点的方法。 - Amir Kirsh
2个回答

3
@Taekahn的评论中提到,我们可以实现所需的行为,尽管这不是自动转换。
// 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);
}

代码:https://godbolt.org/z/_nxmeR


2
我认为答案很简单。在第一种情况下,使用自由函数模板时,首先启动的是重载解析和函数模板参数推导。由于编译器无法从传递的 int 参数中检测出 T(clang sayscandidate template ignored: could not match 'number<type-parameter-0-0>' against 'int'),因此重载解析失败,程序不合法。
当函数被定义为友元时,它是一个非模板函数。编译器在实例化类(对于 number;在 main 中的第一行)时创建它。现在,当它找到它时(使用 ADL),参数类型已经设置好了(都是 number,因为它来自 number 实例化),剩下的就是决定如何将传递的参数从 int 转换为 number,这里使用隐式转换(通过匹配的构造函数)。这里也没有使用 CTAD。
在Effective C++(第三版)中,Scott Meyers讨论了一个类似但不完全相同的案例,该案例涉及到当需要类型转换时应在模板内定义非成员函数。编辑:因此,回答这个问题,函数模板参数推导和隐式类型转换的参数不能混合使用。选择其中一种。(这就是Meyers在提到的那个条目中解释的内容。)

1
Meyers 的参考书籍非常好。虽然有些老旧,但仍然具有相关性。据我所知,在 C++20 中,无法实现模板依赖类型参数的自动转换,正如问题中所要求的那样。我的回答中提供了一种解决方法,但它只是一个权宜之计。 - Amir Kirsh
1
“老而不落伍”应该是 Scott Meyers 所有旧书籍的座右铭 :) 几乎没有关于讨论主题的变化。 - Yehezkel B.

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