Xiaodong Liu
Xiaodong Liu

Reputation: 57

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

Here is my C++ program code

#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);
}

and it prints 1. But what I expect is the function call prints 2. Why B<int>b matches to const T& instead of const A<T>&. And how can I make it to match to const A<T>&?

Upvotes: 2

Views: 941

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275760

Tag dispatching.

First, we write a trait to detect if something has a template as a base:

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

we can now use our new trait to tag dispatch:

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.

Upvotes: 3

Timo
Timo

Reputation: 9835

Solution 1

You could use std::enable_if and templated template parameters to get a better match for the function, like:

#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);
}

However, this works only if A takes exactly the same template parameters as T. So if your B changes to ex.

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

this won't work anymore.

Solution 2

Based on Yakk's answer you can create a type trait that is more generic. This solution has no restrictions to its template parameters, like solution 1 does.

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;

Depending on your c++ version, you can use different approaches to utilize this trait.

C++ 17

Probably the most compact solution. Since C++ 17 constexpr if statements are allowed, which allows us to define just a single 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 and 14

We have to fall back to tag dispatch:

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);
}

Upvotes: 3

Related Questions