繁体   English   中英

Visual C++ 无法推导出模板模板参数

[英]Visual C++ cannot deduce template template parameter

以下 C++17 代码片段在 GCC 和 CLang 中编译,但在 Visual ZF6F87C9FDCF8B3C2F97 中给出了这些错误

<source>(14): error C2672: 'f': no matching overloaded function found
<source>(14): error C2784: 'std::ostream &f(std::ostream &,const container<int> &)': could not deduce template argument for 'const container<int> &' from 'const std::vector<int,std::allocator<int>>'
<source>(5): note: see declaration of 'f'

https://godbolt.org/z/aY769qsfK

#include <vector>

template< template <typename...> typename container >
void f (const container< int > &)
{ }

int main()
{
    std::vector<int> seq = {1, 2, 3};
    f<std::vector>(seq); // OK
    f(seq);              // ERROR
}

请注意,此代码类似于为什么编译器无法推断模板模板参数中的答案之一?

是代码的问题吗? 还是 Visual C++ 中的问题? 在 GCC 和 Visual C++ 中解释的 C++ 标准中可能存在一些歧义?

我在使用 Visual C++ 时也遇到过这种情况,我认为在这方面, Visual C++ 编译器不符合 C++17 标准,并且您的代码使用std::vector :标准容器实际上有两个模板参数:值类型和分配器(默认为std::allocator<T> )。 在 C++17模板匹配之前,模板模板匹配要求模板参数完全匹配,而在C++17中,这被放宽了,包括默认的 arguments 然而由于某种原因,Visual C++ 似乎仍然期望第二个模板参数std::allocator<T>并且不假定给定的默认参数。

以下部分将更详细地讨论不同标准的模板模板匹配。 在帖子的最后,我将建议替代方案,使您的代码在所有上述编译器上编译,这些编译器采用SFINAE的形式,带有两个两个模板 arguments(以便它也可以与自定义分配器一起使用)用于 C++17 和std::span C++20 及更高版本。 std::span实际上根本不需要任何模板。


std:: Containers 的模板参数

正如帖子中指出的那样,您已经链接了标准库容器,例如std::vectorstd::dequestd::list实际上有多个模板参数 第二个参数Alloc是描述 memory 分配的策略特征,并具有默认值std::allocator<T>

template<typename T, typename Alloc = std::allocator<T>>

相反std::array实际上使用两个模板参数T作为数据类型,使用std::size_t N作为容器大小。 这意味着如果想编写一个涵盖所有上述容器的 function ,则必须求助于迭代器 只有在 C++20 中有一个 class 模板用于对象的连续序列std::span (这是一种封装上述所有内容的超级概念),可以放松这一点。

模板模板匹配和 C++ 标准

When writing a function template whose template arguments themselves depend on template parameters you will have to write a so called template template function, meaning a function of the form:

template<template<typename> class T>

请注意,严格按照标准模板模板参数必须使用class声明,而不是在 C++17 之前使用typename 您当然可以使用非常小的解决方案(例如( Godbolt ))以某种方式规避这样的模板模板构造(从 C++11 开始)

template<typename Cont>
void f (Cont const& cont) {
    using T = Cont::value_type;
    return;
}

它假定容器包含一个 static 成员变量value_type ,然后用于定义元素的基础数据类型。 这适用于所有所说的std::容器(包括std::array 。)但不是很干净。

For template template function there exist particular rules which actually changed from C++14 to C++17: Prior to C++17 a template template argument had to be a template with parameters that exactly match the parameters of the template template parameter it substitutes. 未考虑默认 arguments例如std::容器的第二个模板参数,即上述std::allocator<T> (请参阅 此处的“模板模板参数”部分以及“模板模板参数”部分ISO 标准工作草案的第 317 页最终的 C++17 ISO 标准):

要将模板模板实参 A 与模板模板形参 P 匹配,A 的每个模板形参必须完全匹配 P 的对应模板形参(C++17 前) P 必须至少与 A 一样特化(C++ 起17)

Formally, a template template-parameter P is at least as specialized as a template template argument A if, given the following rewrite to two function templates, the function template corresponding to P is at least as specialized as the function template corresponding to A according to function 模板的部分排序规则。 给定一个发明的 class 模板 X,其模板参数列表为 A(包括默认的 arguments ):

  • 两个 function 模板中的每一个都具有相同的模板参数,分别为 P 或 A。
  • Each function template has a single function parameter whose type is a specialization of X with template arguments corresponding to the template parameters from the respective function template where, for each template parameter PP in the template parameter list of the function template, a corresponding template argument AA形成了。 如果 PP 声明了一个参数包,那么 AA 就是包扩展 PP...; 否则,AA 是 id 表达式 PP。

如果重写产生了无效类型,那么 P 至少不像 A 那样专门化。

因此,在 C++17 之前,必须手动编写一个将分配器作为默认值的模板,如下所示。 这也适用于 Visual C++ 但因为以下所有解决方案都将排除std::arrayGodbolt MSVC ):

template<typename T, 
         template <typename Elem,typename Alloc = std::allocator<Elem>> class Cont>
void f(Cont<T> const& cont) {
    return;
}

您也可以在 C++11 中使用可变参数模板(因此数据类型是模板参数包 T 的第一个模板参数,分配器是模板参数包T的第二个模板参数)实现相同的操作,如下所示( Godbolt MSVC ):

template<template <typename... Elem> class Cont, typename... T>
void f (Cont<T...> const& cont) {
    return;
}

现在在 C++17 中,实际上以下行应该编译和使用所有带有std::allocator<T>std::容器(参见第 83-88 页的第 5.7 节,特别是“C++ ”的第 85 页的“模板模板匹配” 模板:Vandevoorde 等人的完整指南(第二版)Godbolt GCC )。

template<typename T, template <typename Elem> typename Cont>
void f (Cont<T> const& cont) {
    return;
}

寻求通用std::容器模板

现在,如果您的目标是使用仅将整数保存为模板 arguments 的通用容器,并且您必须保证它也在 Visual C++ 上编译,那么您有以下选项:

  • 您可以使用static_assert扩展简约的 unclean 版本,以确保您使用正确的值类型 ( Godbolt )。 这应该适用于所有类型的分配器以及std::array但它不是很干净。

     template<typename Cont> void f (Cont const& cont) { using T = Cont::value_type; static_assert(std::is_same<T,int>::value, "Container value type must be of type 'int'"); return; }
  • 您可以将std::allocator<T>添加为默认模板参数,其缺点是,如果有人使用带有自定义分配器的容器并且不会与std::arrayGodbolt )一起使用,那么您的模板将无法工作:

     template<template <typename Elem,typename Alloc = std::allocator<Elem>> class Cont> void f(Cont<int> const& cont) { return; }
  • 与您的代码类似,您可以自己指定分配器作为第二个模板参数。 同样,这不适用于另一种类型的分配器( Godbolt ):

     template<template <typename... Elem> class Cont> void f(Cont<int, std::allocator<int>> const& cont) { return; }
  • 因此,在 C++20 之前,最简洁的方法可能是使用SFINAE到 SFINAE 输出(意味着您在模板中添加某个结构,如果它不满足您的要求则生成编译文件)所有其他不使用具有type_traits的数据类型int (来自#include <type_traits>std::is_sameGodbolt

     template<typename T, typename Alloc, template <typename T,typename Alloc> class Cont, typename std::enable_if<std::is_same<T,int>::value>::type* = nullptr> void f(Cont<T,Alloc> const& cont) { return; }

    或者不是 integer 类型( std::is_integralGodbolt ),因为这对于模板参数Alloc更加灵活:

     template<typename T, typename Alloc, template <typename T,typename Alloc> class Cont, typename std::enable_if<std::is_integral<int>::value>::type* = nullptr> void f(Cont<T,Alloc> const& cont) { return; }

    此外,这可以通过逻辑或||轻松扩展。 和逻辑和&& 由于 C++14 也可以使用相应的别名并编写std::enable_if_t<std::is_same_v<T,int>>而不是std::enable_if<std::is_same<T,int>::value>::type这使得阅读起来不那么尴尬。

  • 最后,在最新的标准C++20中,您甚至应该能够使用期待已久的概念( #include <concepts>使用Container 概念(另请参阅此Stackoverflow 帖子),例如如下( Wandbox

     template<template <typename> typename Cont> requires Container<Cont<int>> void f(Cont<int> const& cont) { return; }
  • 与 C++20 类似,存在std::span<T>与上述所有解决方案不同,它也适用于std::arrayWandbox

     void f(std::span<int> const& cont) { return; }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM