为什么 C++ 模板类型匹配与 base class 引用不匹配,如何使其与 base class 引用匹配?

why C++ template type matching does not match to base class refrence, how can I make it match to base class refrence?

这是我的C++程序代码

#include <iostream>
#include <memory>
using namespace std;

template <typename T>
struct A { T x; };

template <typename T>
struct B:A<T> {  };

template <typename T>
void func(const A<T>& t) {
    cout<<"2"<<endl;
}

template <typename T>
void func(const T& t) {
    cout<<"1"<<endl;
}

int main() {
    B<int> b;
    func(b);
}

并打印 1。但我期望的是函数调用打印 2。为什么 B<int>b 匹配 const T& 而不是 const A<T>&。我怎样才能让它匹配 const A<T>&?

解决方案 1

您可以使用 std::enable_if 和模板化模板参数来更好地匹配函数,例如:

#include <iostream>
#include <memory>
#include <type_traits>

using namespace std;

template <typename T>
struct A { T x; };

template <typename T>
struct B:A<T> {  };

template <template <typename...> typename T, typename ...Args, typename = std::enable_if_t<std::is_base_of_v<A<Args...>, T<Args...>>>>
void func(const T<Args...>& t) {
    cout<<"2"<<endl;
}

template <typename T>
void func(const T& t) {
    cout<<"1"<<endl;
}


int main() {
    B<int> b;
    func(b);
    func(5);
}

然而,这仅在 A 采用与 T 完全相同的模板参数时有效。因此,如果您的 B 更改为 ex。

template <typename T, typename U>
struct B : A<T> {}

这将不再有效。

解决方案 2

基于,您可以创建更通用的类型特征。此解决方案对其模板参数没有限制,就像解决方案 1 一样。

namespace detail 
{
    template <template <typename...> typename Base>
    struct template_base_detector
    {
        template <typename... Args>
        constexpr std::true_type operator()(Base<Args...>*);
        constexpr std::false_type operator()(...);
    };
} 

template <template <typename...> typename Base, typename T>
struct is_template_base_of 
    : decltype(std::declval<detail::template_base_detector<Base>>()((T*)nullptr)) {};

// since C++ 14
template <template <typename...> typename Base, typename T>
constexpr bool is_template_base_of_v = is_template_base_of<Base, T>::value;

根据您的 c++ 版本,您可以使用不同的方法来利用此特性。

C++ 17

可能是最紧凑的解决方案。由于 C++ 17 constexpr if 语句是允许的,这允许我们只定义一个 func:

template <typename T>
void func(const T& t) 
{
    if constexpr (is_template_base_of_v<A, T>)
        cout << 2 << endl;
    else
        cout << 1 << endl;
}

C++ 11 和 14

我们必须退回到标签分发:

namespace detail 
{
    template <typename T>
    void func(std::true_type, const T& t) 
    {
        std::cout << 2 << endl;
    }

    template <typename T>
    void func(std::false_type, const T& t) 
    {
        std::cout << 1 << endl;
    }
}

template <typename T>
void func(const T& t) 
{
    detail::func(is_template_base_of<A, T>{}, t);
}

代码调度。

首先,我们编写一个特征来检测是否有模板作为基础:

namespace details {
  template<template<class...>class Z>
  struct htb {
    template<class...Ts>
    constexpr std::true_type operator()(Z<Ts...>*){return {};}
    constexpr std::false_type operator()(...){return {};}
  };
}
template<template<class...>class Z, class X>
constexpr inline auto has_template_base = details::htb<Z>{}((X*)nullptr);

我们现在可以使用我们的新特征来标记调度:

namespace details{
  template <typename T>
  void func(std::true_type,const A<T>& t) {
    std::cout<<"2"<<std::endl;
  }

  template <class T>
  void func(std::false_type,const T& t) {
    std::cout<<"1"<<std::endl;
  }
}

template <typename T>
void func(const T& t) {
  details::func(has_template_base<A,T>,t);
}

Live example.