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

jhkqcmku  于 2024-01-09  发布在  其他
关注(0)|答案(1)|浏览(162)

我的代码在这里:

  1. #include <iostream>
  2. #include <memory>
  3. #include <queue>
  4. template<typename TValue>
  5. [[maybe_unused]]
  6. constexpr auto t1(const std::queue<TValue> &value) -> void {
  7. std::queue<TValue> temp = value;
  8. while (!temp.empty()) {
  9. std::cout << temp.front() << std::endl;
  10. temp.pop();
  11. }
  12. }
  13. template<typename TValue>
  14. constexpr auto t1(const TValue &nm) -> void {
  15. std::cout << nm << std::endl;
  16. }
  17. template<typename TValue>
  18. constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
  19. t1<TValue>(*nm);
  20. }
  21. int main(int argc, char **argv) {
  22. std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7);
  23. t1(s_ptr);
  24. return 0;
  25. }

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

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


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

  1. template<typename TValue>
  2. constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
  3. if constexpr (std::is_same_v<TValue, const int>){
  4. static_assert(false, "??");
  5. }
  6. t1<TValue>(*nm);
  7. }


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

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


或者:

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


还有:

  1. template<typename TValue>
  2. constexpr auto t1(const TValue &nm) -> void {
  3. std::cout << nm << std::endl;
  4. }
  5. template<typename TValue>
  6. constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void {
  7. t1<TValue>(*nm);
  8. }
  9. int main(int argc, char **argv) {
  10. std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7);
  11. t1(s_ptr);
  12. return 0;
  13. }


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

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

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

  1. #include <type_traits>
  2. template <typename T>
  3. struct container {
  4. static_assert(not std::is_const_v<T>);
  5. };
  6. template<typename T> void foo(container<T>);
  7. template<typename T> void foo(T);
  8. int main() {
  9. foo<const int>(0);
  10. }

个字符

溶液

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

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


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,同时具有相同的模板参数:

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


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

展开查看全部

相关问题