模板模板参数的显式匹配

时间:2019-04-22 22:42:31

标签: c++ c++14 c++17 template-deduction template-templates

考虑此功能:

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失败?

3 个答案:

答案 0 :(得分:3)

  

如何使foo仅接受仅具有2个模板参数的模板,因此在C ++ 17中它无法通过std :: map失败?

如果要避免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
 }

答案 1 :(得分:2)

创建函数的模板重载,该函数将容器包含三个元素。当您尝试使用带有两个参数的容器时,它将起作用;当您尝试使用带有三个参数的东西,而第三个具有默认值(例如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{};

因为它将仅匹配三个参数选项,所以不会造成歧义

答案 2 :(得分:1)

添加使用可变参数模板参数而不是两个固定参数的一级间接。然后,只要计数不为两个,就可以使用旧的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 clanglatest msvc尚未正确处理。 clang (experimental concepts)可以。