c++ 为什么重载解析在显式提供模板参数时会选择错误的重载?

jhkqcmku  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(112)

我的代码在这里:

#include <iostream>
#include <memory>
#include <queue>

template<typename TValue>
[[maybe_unused]]
constexpr auto t1(const std::queue<TValue> &value) -> void {
    std::queue<TValue> temp = value;
    while (!temp.empty()) {
        std::cout << temp.front() << std::endl;
        temp.pop();
    }
}

template<typename TValue>
constexpr auto t1(const TValue &nm) -> void {
    std::cout << nm << std::endl;
}

template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
    t1<TValue>(*nm);
}

int main(int argc, char **argv) {
    std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7);
    t1(s_ptr);

    return 0;
}

字符串
此代码无法编译(https://godbolt.org/z/crvKb7rEz):
错误C2338:static_assert失败:'C++标准禁止const元素的容器,因为分配器格式不正确。'
我试着用shared_ptr修改模板,如下所示:

template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
    const int temp = *nm;
    t1<TValue>(temp);
}


它会导致同样的错误。我还尝试获取'TValue'类型:

template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
    if constexpr (std::is_same_v<TValue, const int>){
        static_assert(false, "??");
    }
    t1<TValue>(*nm);
}


static_assert被触发。这意味着'TValue'是'const std::string'。
不小心,我删除了<TValue>这样:

template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
    t1(*nm);
}


或者:

template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
    t1<std::remove_cv_t<TValue>>(*nm);
}


还有:

template<typename TValue>
constexpr auto t1(const TValue &nm) -> void {
    std::cout << nm << std::endl;
}

template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
    t1<TValue>(*nm);
}

int main(int argc, char **argv) {
    std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7);
    t1(s_ptr);

    return 0;
}


所有这些工作。
为什么模板要使用std::queue重载时,<TValue> euqal到const int?我除了它使用:

template<typename TValue>
constexpr auto t1(const TValue &nm) -> void {
    std::cout << nm << std::endl;
}

zysjyyx4

zysjyyx41#

问题是你显式地提供了模板参数TValue,这导致了std::deque<const std::string>的示例化。std::deque<T>需要一个非常量、非易失性的T,正如失败的静态Assert所说:
错误:static assertion failed:std::deque must have a non-const,non-volatile value_type

最小可复制示例

为了说明发生了什么,这里有一个简化版本的问题(https://godbolt.org/z/zYPhhb5dq):

#include <type_traits>

template <typename T>
struct container {
    static_assert(not std::is_const_v<T>);
};

template<typename T> void foo(container<T>);
template<typename T> void foo(T);

int main() {
    foo<const int>(0);
}

个字符

溶液

你可以写t1(*nm)。这里的区别是std::deque<const std::string>从来没有示例化过。相反,替换到t1(std::deque)会失败,因为*nmconst std::string,而std::deque<TValue>中的TValue不能从const std::string推导出来。

template<typename TValue>
constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
    t1(*nm); // OK
}


t1<std::remove_cv_t<TValue>>(*nm);也可以工作(尽管你不应该写它),因为你示例化了std::deque<std::string>。这是有效的,但是,重载解析将选择t1(const T&),因为没有从std::stringstd::deque的隐式转换。

**你应该写t1(*nm);**通常最好推导模板参数,而不是显式提供它们。

SFINAE友好性说明

潜在的问题是static_assert不是SFINAE友好的,也就是说,它会导致 * 后 * 替换的错误。在C++20中,可以使用约束代替static_assert,同时具有相同的模板参数:

template <typename T>
  requires (not std::is_const_v<T>) // analogous for std::deque
struct container { };


如果这样做,foo<const int>(0)是有效的(https://godbolt.org/z/TWKxs99Yj)。
我不认为std::deque有这样的约束是法律的,如果有,你的代码就可以工作了。

相关问题