模板模板参数的显式匹配

Explicit match of template template arguments

考虑这个函数:

template<template<class, class> class C, class T, class Alloc>
void foo(C<T, Alloc>& container) {
    std::cout << container.size() << std::endl;
}

函数 foo() 接受 std::vector<T, Alloc>,但在 C++17 中它也接受 std::map<Key, T, Compare, Allocator>,因为 CompareAllocator 具有默认值。注意 std::map 在 C++14 中失败。

如何让 foo() 只接受恰好只有 2 个模板参数的模板,所以它在 C++17 中失败并显示 std::map

创建函数的模板重载,该函数采用包含三个元素的容器。当您尝试使用具有两个参数的容器时,它会起作用,当您尝试使用具有三个参数且第三个参数具有默认值(如 std::map)的容器时,它将失败(因为任何一个都可能使它过载)。

#include <map>
#include <vector>
#include <iostream>

template<template<class, class, class> class C, class Key, class T, class Alloc>
void foo(C<Key, T, Alloc>& container) {
    std::cout << "three arguments" << std::endl;
}

template<template<class, class> class C, class T, class Alloc>
void foo(C<T, Alloc>& container) {
    std::cout << "two arguments" << std::endl;
}



int main() {
    std::vector<int> v;
    std::map<int,int> m;

    foo(v);
    foo(m);
}

请注意,如果有人输入类似

的内容,这将不起作用
template <class A, class B, class C> bar{};

因为那只会匹配三个参数选项,所以不会有歧义

How can I make foo only accept templates with exactly only 2 template parameters, so it fails with std::map in C++17?

如果你想避免 foo() 接受接受三个或更多模板参数的容器(因此 std::map 失败)相对简单:你可以遵循 rtpax 的建议,或者给定一个自定义类型表示类型是否基于两种类型接受容器的特征

template <typename>
struct accept2 : std::false_type
 { };

template <template <typename...> class C, typename X, typename Y>
struct accept2<C<X, Y>> : std::true_type
 { };

和三接受容器的类似类型特征

template <typename>
struct accept3 : std::false_type
 { };

template <template <typename...> class C, typename X, typename Y, typename Z>
struct accept3<C<X, Y, Z>> : std::true_type
 { };

仅当推导类型接受两种但不接受三种类型时,您才能启用 SFINAE foo()

template <typename C>
std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const & container)
 { }

但是这个解决方案(以及 rtpax 解决方案)有一​​个问题:如果容器在两个模板类型之前和一个(或多个)具有默认值的模板非类型参数之后接收怎么办?

或者两种模板类型和一种(或多种)具有默认值的模板-模板参数?也许有不同的签名?

您可以为 accept3 添加特殊化以识别其他情况,但是类型、非类型和模板模板参数的无限组合,在前两个模板类型之后具有默认值。所以你必须编写无限特化来拦截所有情况。

这有点不切实际。

而且我怀疑(在 C++17 中)没有实用的解决方案。

无论如何,下面是一个完整的编译示例(避免三个或更多模板类型容器)

#include <map>
#include <vector>
#include <type_traits>

template <typename>
struct accept2 : std::false_type
 { };

template <template <typename...> class C, typename X, typename Y>
struct accept2<C<X, Y>> : std::true_type
 { };

template <typename>
struct accept3 : std::false_type
 { };

template <template <typename...> class C, typename X, typename Y, typename Z>
struct accept3<C<X, Y, Z>> : std::true_type
 { };

template <typename C>
std::enable_if_t<accept2<C>{} && !accept3<C>{}> foo (C const &)
 { }

int main()
 {
   std::vector<int> v;
   std::map<int,int> m;

   foo(v);   // compile
   //foo(m); // compilation error
 }

添加一个使用可变参数模板参数而不是两个固定参数的间接级别。然后你可以使用旧的 enable_if 在计数不是两个时禁用它:

template<template<class...> class C, class T, class Alloc>
void foo_impl(C<T, Alloc>& container) {
    std::cout << container.size() << std::endl;
}

template<template<class...> class C, class... Args>
std::enable_if_t<sizeof...(Args) == 2> foo(C<Args...>& container) {
    foo_impl(container);
}

查看here它是如何工作的。

旁注:显然,latest clang and latest msvc do not handle it correctly yet. clang (experimental concepts) 确实如此。