2

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

尝试调用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 :(
}

任何建议如何允许模板类的行为类似于非模板,在调用 add 时自动转换?


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

4

2 回答 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

于 2020-07-07T19:56:05.453 回答
2

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

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

Scott Meyers 在 Effective C++ (3rd edition), Item 46: Define non-member functions inside templates when type conversions 中讨论了类似(但不完全相同)的情况。

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

于 2020-07-07T19:21:01.523 回答