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

标签 c++ templates c++17 std shared-ptr

我的代码在这里:

#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 ):

error C2338: static_assert failed: 'The C++ Standard forbids containers of const elements because allocator is ill-formed.'

我尝试使用 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>等于const int ?我除了它使用:

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

最佳答案

问题是您显式提供了模板参数 TValue这会导致 std::deque<const std::string> 的实例化。 std::deque<T> requires a non-const, non-volatile T ,正如失败的静态断言所示:

error: static assertion failed: std::deque must have a non-const, non-volatile value_type

-https://godbolt.org/z/PGY7nKW57

最小可重现示例

为了说明发生了什么,这是您的问题的简化版本 ( 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);
}
error: static assertion failed due to requirement '!std::is_const_v<const int>'
    5 |     static_assert(not std::is_const_v<T>);
      |                   ^~~~~~~~~~~~~~~~~~~~~~
note: in instantiation of template class 'container<const int>' requested here
   15 |     foo<const int>(0);
      |

解决方案

您可以写t1(*nm)反而。 这里的区别是std::deque<const std::string>从未被实例化。 相反,替换为 t1(std::deque)会失败,因为 *nmconst std::stringTValuestd::deque<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::string 进行隐式转换至std::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 是合法的有这样的限制。 如果有它们,您的代码就可以工作。

关于c++ - 为什么在显式提供模板参数时重载解析会选择错误的重载?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77751095/

相关文章:

c++ - 在标准布局对象(例如,使用 offsetof)中进行指针运算时,我们是否需要使用 std::launder?

c++ - 了解模板内定义的模板 (C++)

在for循环中创建的C++多态指针指的是同一个东西,这是因为我没有使用智能指针吗?

c++ - struct int数据类型不适用于关系运算符

c++ - 如何检查类型是否可显式/隐式构造?

c++ - C++ 中哪些类型被认为是可调用的?

c++ - Netbeans C++ 程序没有终端输出

C++ 模板化类给出 `error: non-template X used as template`

c++ - 无论如何将多个if条件与c++模板链接在一起?

c++ - 我可以要求编译器禁止定义模板类成员函数的泛型版本吗?