Class 模板相关参数的模板参数推导

Class Template Argument Deduction for template dependent parameter

让我们从一个简单的添加方法开始 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
}

现在我们要将number变成模板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;
}

尝试调用 add 就像我们调用简单的非模板的一样,基于(理论上!)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
}

请注意,如果 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 :(
}

有什么建议可以让模板 class 的行为类似于非模板,在调用添加时自动转换?


编辑: 这个问题是根据发表的评论编辑的。应该强调的是,对于像 add 这样的通用函数,具有这样的自动转换可能是一个错误的想法,但假设该方法非常具体,例如 doSomethingWithNumbers.

我认为答案很简单。在第一种情况下,使用自由函数模板,首先启动的是重载解析和函数模板参数推导。由于编译器无法从作为参数传递的 int 中检测到 T (clang says: candidate template ignored: could not match 'number<type-parameter-0-0>' against 'int'),因此重载解析失败并且程序格式错误。

当函数定义为friend时,它是一个非模板函数。编译器在实例化 class 时创建它(对于 number<int>main 中的第一行)。现在,当它找到它时(使用 ADL),参数类型已经设置好(都是 number<int> 因为它来自 number<int> 实例化),剩下的就是决定如何转换传递的参数从 intnumber<int>,其中使用了隐式转换(通过匹配的 c-tor)。这里也没有 CTAD。

Scott Meyers 在 Effective C++(第 3 版)第 46 项:需要类型转换时在模板中定义非成员函数时讨论了类似(但不完全相同)的情况。

编辑: 所以要回答这个问题,函数模板参数推导和参数的隐式类型转换不能混合使用。选一个。 (这是迈耶斯在提到的项目中解释的内容。)

行中,我们可以实现所需的行为,尽管它不是自动转换:

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