Polymorphic visitor with lambdas - c++

I want to implement a polymorphic visitor using lambdas without implementing a class. I already have a foundation but am struggling with the type deduction for the parameters of my lambdas.
Let's say I have some legacy code base that decided to use type tags for a polymorphic type like so:
enum class ClassType
{
BaseType = 0, TypeA, TypeB
};
class BaseType
{
public:
virtual ~BaseType() {}
ClassType getType() const
{ return type; }
protected:
ClassType type;
};
class TypeA : public BaseType
{
public:
static const ClassType Type = ClassType::TypeA;
explicit TypeA(int val) : val(val)
{ type = ClassType::TypeA; }
virtual ~TypeA() {}
int val;
};
class TypeB : public BaseType
{
public:
static const ClassType Type = ClassType::TypeB;
explicit TypeB(std::string s) : s(s)
{ type = ClassType::TypeB; }
virtual ~TypeB() {}
std::string s;
};
What I want to achieve is a visitor similar to the std::variant visitors that would then look like this:
std::vector<BaseType*> elements;
elements.emplace_back(new TypeA(1));
elements.emplace_back(new TypeB("hello"));
for (auto elem : elements)
{
visit(elem,
[](TypeA* typeA) {
std::cout << "Found TypeA element, val=" << typeA->val << std::endl;
},
[](TypeB* typeB) {
std::cout << "Found TypeB element, s=" << typeB->s << std::endl;
}
);
}
My so far failing approach for implementing such a visit<>() function was the following code:
template <typename T>
struct identity
{
typedef T type;
};
template <typename T>
void apply_(BaseType* b, typename identity<std::function<void(T*)>&>::type visitor)
{
if (b->getType() != T::Type)
return;
T* t = dynamic_cast<T*>(b);
if (t) visitor(t);
}
template <typename... Ts>
void visit(BaseType* b, Ts... visitors) {
std::initializer_list<int>{ (apply_(b, visitors), 0)... };
}
The compiler complains that it cannot deduce the template parameter T for my apply_ function.
How can I declare the correct template and function signature of apply_ to correctly capture lambdas and maybe even other callables? Or is something like this even possible at all?

Here's an (incomplete) solution that works with any function object that has an unary, non-overloaded, non-templated operator(). Firstly, let's create an helper type alias to retrieve the type of the first argument:
template <typename>
struct deduce_arg_type;
template <typename Return, typename X, typename T>
struct deduce_arg_type<Return(X::*)(T) const>
{
using type = T;
};
template <typename F>
using arg_type = typename deduce_arg_type<decltype(&F::operator())>::type;
Then, we can use a fold expression in a variadic template to call any function object for which dynamic_cast succeeds:
template <typename Base, typename... Fs>
void visit(Base* ptr, Fs&&... fs)
{
const auto attempt = [&](auto&& f)
{
using f_type = std::decay_t<decltype(f)>;
using p_type = arg_type<f_type>;
if(auto cp = dynamic_cast<p_type>(ptr); cp != nullptr)
{
std::forward<decltype(f)>(f)(cp);
}
};
(attempt(std::forward<Fs>(fs)), ...);
}
Usage example:
int main()
{
std::vector<std::unique_ptr<Base>> v;
v.emplace_back(std::make_unique<A>());
v.emplace_back(std::make_unique<B>());
v.emplace_back(std::make_unique<C>());
for(const auto& p : v)
{
visit(p.get(), [](const A*){ std::cout << "A"; },
[](const B*){ std::cout << "B"; },
[](const C*){ std::cout << "C"; });
}
}
ABC
live example on wandbox

Assuming that you cannot change the virtual classes, you may do the following:
template <typename F>
decltype(auto) visitBaseType(BaseType& base, F&& f)
{
switch (base.getType())
{
case ClassType::BaseType: return f(base);
case ClassType::TypeA: return f(dynamic_cast<TypeA&>(base));
case ClassType::TypeB: return f(dynamic_cast<TypeB&>(base));
}
throw std::runtime_error("Bad type");
}
template<class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
overloaded(Ts... ts) : Ts(ts)... {}
};
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template <typename ... Fs>
decltype(auto) visit(BaseType& base, Fs&&... fs)
{
return visitBaseType(base, overloaded(fs...));
}
Demo

I don't always say this, but this may be a job for the Boost.Preprocessor. You have a list of class types that corresponds to a list of enums, each instance identifies itself via getType(). So we can use that:
#include <boost/preprocessor/seq/for_each.hpp>
#define CLASS_LIST (TypeA) (TypeB)
// just take one visitor
template <class Visitor>
void visit(Base* ptr, Visitor f) {
switch (ptr->getType()) {
#define CASE_ST(r, data, elem) case elem: f(static_cast<elem*>(ptr)); break;
BOOST_PP_SEQ_FOR_EACH(CASE_ST, ~, CLASS_LIST)
#undef CASE_ST
default: f(ptr); // in case you want an "else"
// this is optional
}
}
That will preprocess into:
switch (ptr->getType()) {
case TypeA: f(static_cast<TypeA*>(ptr)); break;
case TypeB: f(static_cast<TypeB*>(ptr)); break;
default: f(ptr);
}

Related

C++ - How to call a templated method of a recursively inherited templated base class

I'm trying to create a Tuple class with an arbitrary number of entries with arbitrary types using variadic templates and the ability to get the nth entry with a templatized entry method, so I can use it as follows:
Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);
std::cout << t.entry<1>() << std::endl; // Prints 2
std::cout << t.entry<2>() << std::endl; // Prints "Hello World"
My current approach:
template<typename ...Types>
struct Tuple;
template<typename Type>
struct Tuple<Type>
{
Tuple(Type value) : value(value) { };
Type value;
template<int Index>
Type& entry()
{
return value;
}
};
template<typename Type, typename... Types>
struct Tuple<Type, Types...> : public Tuple<Types...>
{
Tuple(Type value, Types ...args) : Tuple<Types...>(args...), value(value) { }
Type value;
template<int Index>
auto entry() -> decltype(Tuple<Types...>::entry<Index-1>())&
{
return Tuple<Types...>::entry<Index-1>();
}
template<>
Type& entry<0>()
{
return value;
}
};
The first struct providing the "base" case for one element and the second struct building recursively upon that. However, I get the error
In member function ‘decltype (((Tuple<Types ...>::entry < (Index - 1)) > <expression error>))& Tuple<Type, Types ...>::entry()’:
error: expected primary-expression before ‘)’ token
How can I call a templated method of a templated base class?
Assuming you're using at least C++14, you can use auto& instead of a trailing return type, and as pointed out by max66 you need to add the template keyword before the member function invocation in your syntax.
You can also simplify your definition because you just need an empty base class, not a class which implements a specialization for one type; your second class already implements the necessary behavior for one type. This simplification requires you to rewrite entry() using std::enable_if, or if constexpr if you're using C++17
// only needed for C++14 when using std::enable_if
#include <type_traits>
template<typename...>
struct Tuple
{
};
template<typename T, typename... Ts>
struct Tuple<T, Ts...> : public Tuple<Ts...>
{
Tuple(T value, Ts ...args) : value(value), Tuple<Ts...>(args...) { }
T value;
template<std::size_t I, std::enable_if_t<I == 0, bool> = true>
T& entry() { return value; }
template<std::size_t I, std::enable_if_t<I != 0, bool> = true>
auto& entry() { return Tuple<Ts...>::template entry<I - 1>(); }
// // requires C++17 support
// template<std::size_t I>
// auto& entry() {
// if constexpr (I == 0) { return value; }
// else { return Tuple<Ts...>::template entry<I - 1>(); }
// }
};
Try it on godbolt.org
You need add a template before ::entry
template<int Index> // ...................VVVVVVVVV
auto entry() -> decltype(Tuple<Types...>::template entry<Index-1>())&
{ // .......................VVVVVVVVV
return Tuple<Types...>::template entry<Index-1>();
}
or the < after ::entry is parsed as a relational operator.
But you have another problem: the specialization of entry():
template<>
Type& entry<0>()
{
return value;
}
Unfortunately you can't specialize the a method without specializing the containing class.
If you can compile C++17, you can avoid method specialization and use if constexpr
template <int Index>
auto & entry()
{
if constexpr ( Index == 0 )
return value;
else
return Tuple<Types...>::template entry<Index-1>();
}
Pre C++17 In C++14... I suppose you can solve using tag dispatching
template <int>
Type & entry_helper (std::true_type)
{ return value; }
template <int Index>
auto & entry_helper (std::false_type)
{ return Tuple<Types...>::template entry<Index-1>(); }
template <int Index>
auto & entry()
{ return entry_helper<Index>(std::integral_constant<bool, Index==0>{}); }
In C++11 you need also the trailing return type in for entry() and for the second entry_helper()
As pointed by Patrick Roberts (thanks!) the solution, adding trailing return type, works for C++11 with g++ but not for clang++, for a problem in detecting the return type in a context of recursion.
For C++11 I propose a completely different solution that avoid the entry()/entry_helper() recursion but add another level of indirection at class level (add a recursive base class struct Tpl). Add also perfect forwarding, unsigned indexes and const-versions for entry() and entry_helper().
#include <utility>
#include <iostream>
#include <type_traits>
template <std::size_t, typename...>
struct Tpl
{ void entry_helper () {} };
template <std::size_t I, typename T, typename ... Ts>
struct Tpl<I, T, Ts...> : public Tpl<I+1u, Ts...>
{
using Tpl<I+1, Ts...>::entry_helper;
Tpl (T && t, Ts && ... ts)
: Tpl<I+1u, Ts...>{std::forward<Ts>(ts)...}, value{std::forward<T>(t)}
{ }
T value;
T & entry_helper (std::integral_constant<std::size_t, I>)
{ return value; }
T const & entry_helper (std::integral_constant<std::size_t, I>) const
{ return value; }
};
template <typename ... Ts>
struct Tuple : public Tpl<0, Ts...>
{
using Tpl<0, Ts...>::entry_helper;
Tuple (Ts && ... ts) : Tpl<0u, Ts...>{std::forward<Ts>(ts)...}
{ }
template <std::size_t I>
auto entry ()
-> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) &
{ return entry_helper(std::integral_constant<std::size_t, I>{}); }
template <std::size_t I>
auto entry () const
-> decltype(entry_helper(std::integral_constant<std::size_t, I>{})) const &
{ return entry_helper(std::integral_constant<std::size_t, I>{}); }
};
int main()
{
Tuple<int, int, std::string, double> t(1, 2, "Hello World", 3.4);
std::cout << t.entry<1>() << std::endl; // Prints 2
std::cout << t.entry<2>() << std::endl; // Prints "Hello World"
}

Composite pattern of std::functions

I am trying to implement a composite pattern for std::functions with use of template classes, where each composite class processes the return values of its children.
So the pattern classes might look something like this:
class AbstractClass {
public:
virtual void process() = 0;
};
template<typename ReturnType>
class PrimitiveClass : public AbstractClass {
public:
ReturnType process() {
// please note, that the result is not returned by the return statement
return this->func(); //this is just for simplicity
}
private:
std::function<ReturnType()> func;
}
template<typename ReturnType, typename ...Args>
class CompositeClass : public AbstractClass {
public:
ReturnType process() {
// --> This is where I want to process all children first and then pass their return values to this->func
// the following code is kind of a pseudo code:
for(auto it = vector.begin(); it != vector.end(); ++it {
results.add((**it).process())
}
return this->func(results)
}
private:
std::function<ReturnType(Args...)> func;
std::vector<std::shared_ptr<AbstractClass>> children;
};
So for example, I have a CompositeClass with a std::function<int(int, double, bool) and the argument types of that function are also the ReturnTypes of its children. And I want to pass the return values of the children to above-mentioned std::function
Can anyone think of a way, how I can achieve this?
If I understand what you want (and if I'm not wrong)...
(1) to solve the problem of the no-covariant returned value from process() (see comment from Igor Tandetnik) you need a template abstract class to express the correct return value; by example
template <typename T>
struct abstClass
{ virtual T process() const = 0; };
(2) so your CompositeClass (renamed nodeClass, in my following example) inherit from abstClass<ReturnType>
(3) your PrimitiveClass is useless because you can manage the case (reference to a function without arguments) as a CompositeClass with zero Args
(4) you need a leafClass to handle basic values
(5) in CompositeClass (nodeClass), children, instead of a std::vector of shared_ptr<AbstractClass> (that can't do what do you want), can be a
std::tuple<std::shared_ptr<abstClass<Args>>...> children;
Given these points, I propose the following solution (that, unfortunately, is C++14 because use std::index_sequence and std::make_index_sequence that are available starting from C++14; but if you need a C++11 solution, isn't difficult write substitutes for they)
#include <tuple>
#include <memory>
#include <iostream>
#include <functional>
template <typename T>
struct abstClass
{ virtual T process() const = 0; };
template <typename T>
class leafClass : public abstClass<T>
{
private:
T value;
public:
leafClass (T && v0) : value { std::forward<T>(v0) }
{ }
T process () const
{ return value; };
};
template <typename RetT, typename ... ArgTs>
class nodeClass : public abstClass<RetT>
{
private:
using funcT = std::function<RetT(ArgTs...)>;
template <typename T>
using shrPAC = std::shared_ptr<abstClass<T>>;
funcT func;
std::tuple<shrPAC<ArgTs>...> childrens;
template <std::size_t ... Is>
RetT processH (std::index_sequence<Is...> const &) const
{ return func(std::get<Is>(childrens)->process()...); }
public:
nodeClass (funcT && f0, shrPAC<ArgTs> && ... as)
: func { std::forward<funcT>(f0) },
childrens { std::forward<shrPAC<ArgTs>>(as)... }
{ }
RetT process () const
{ return processH(std::make_index_sequence<sizeof...(ArgTs)>{}); }
};
int main ()
{
auto func0 = [](int i, double d, bool b) { return int( b ? i+d : i-d ); };
auto shpLci = std::make_shared<leafClass<int>>(1);
auto shpLcd = std::make_shared<leafClass<double>>(2.2);
auto shpNb = std::make_shared<nodeClass<bool>>([](){ return true; });
auto shpNc0 = std::make_shared<nodeClass<int, int, double, bool>>
(func0, shpLci, shpLcd, shpNb);
auto shpNc1 = std::make_shared<nodeClass<int, int, double, bool>>
(func0, shpNc0, shpLcd, shpNb);
auto shpNc2 = std::make_shared<nodeClass<int, int, double, bool>>
(func0, shpNc1, shpLcd, shpNb);
std::cout << shpNc0->process() << std::endl; // print 3
std::cout << shpNc1->process() << std::endl; // print 5
std::cout << shpNc2->process() << std::endl; // print 7
}

C++ method with multiple parameter packs

Consider the following simplified piece of code for a variant class. Most of it is for informational purposes, the question is about the conditional_invoke method.
// Possible types in variant.
enum class variant_type { empty, int32, string };
// Actual data store.
union variant_data {
std::int32_t val_int32;
std::string val_string;
inline variant_data(void) { /* Leave uninitialised */ }
inline ~variant_data(void) { /* Let variant do clean up. */ }
};
// Type traits which allow inferring which type to use (these are actually generated by a macro).
template<variant_type T> struct variant_type_traits { };
template<class T> struct variant_reverse_traits { };
template<> struct variant_type_traits<variant_type::int32> {
typedef std::int32_t type;
inline static type *get(variant_data& d) { return &d.val_int32; }
};
template<> struct variant_reverse_traits<std::int32_t> {
static const variant_type type = variant_type::int32;
inline static std::int32_t *get(variant_data& d) { return &d.val_int32; }
};
template<> struct variant_type_traits<variant_type::string> {
typedef std::string type;
inline static type *get(variant_data& d) { return &d.val_string; }
};
template<> struct variant_reverse_traits<std::string> {
static const variant_type type = variant_type::string;
inline static std::string *get(variant_data& d) { return &d.val_string; }
};
// The actual variant class.
class variant {
public:
inline variant(void) : type(variant_type::empty) { }
inline ~variant(void) {
this->conditional_invoke<destruct>();
}
template<class T> inline variant(const T value) : type(variant_type::empty) {
this->set<T>(value);
}
template<class T> void set(const T& value) {
this->conditional_invoke<destruct>();
std::cout << "Calling data constructor ..." << std::endl;
::new (variant_reverse_traits<T>::get(this->data)) T(value);
this->type = variant_reverse_traits<T>::type;
}
variant_data data;
variant_type type;
private:
template<variant_type T> struct destruct {
typedef typename variant_type_traits<T>::type type;
static void invoke(type& v) {
std::cout << "Calling data destructor ..." << std::endl;
v.~type();
}
};
template<template<variant_type> class F, class... P>
inline void conditional_invoke(P&&... params) {
this->conditional_invoke0<F, variant_type::int32, variant_type::string, P...>(std::forward<P>(params)...);
}
template<template<variant_type> class F, variant_type T, variant_type... U, class... P>
void conditional_invoke0(P&&... params) {
if (this->type == T) {
F<T>::invoke(*variant_type_traits<T>::get(this->data), std::forward<P>(params)...);
}
this->conditional_invoke0<F, U..., P...>(std::forward<P>(params)...);
}
template<template<variant_type> class F, class... P>
inline void conditional_invoke0(P&&... params) { }
};
The code works this way, i.e. it works as long as the parameter list P... for the functor is empty. If I add another functor like
template<variant_type T> struct print {
typedef typename variant_type_traits<T>::type type;
static void invoke(type& v, std::ostream& stream) {
stream << v;
}
};
and try to invoke it
friend inline std::ostream& operator <<(std::ostream& lhs, variant& rhs) {
rhs.conditional_invoke<print>(lhs);
return lhs;
}
the compiler VS 20115 complains
error C2672: 'variant::conditional_invoke0': no matching overloaded function found
or gcc respectively
error: no matching function for call to 'variant::conditional_invoke0 >&>(std::basic_ostream&)'
I guess the compiler cannot decide when U... ends and when P... starts. Is there any way to work around the issue?
You'll have to make both parameter packs deducible. That is, let the type and non-type template parameters be part of a function parameter list. For that, introduce a dummy structure:
template <variant_type...>
struct variant_type_list {};
and let the compiler deduce the variant_type... pack from a function call:
template <template <variant_type> class F
, variant_type T
, variant_type... U
, typename... P>
void conditional_invoke0(variant_type_list<T, U...> t
, P&&... params)
{
if (this->type == T)
{
F<T>::invoke(*variant_type_traits<T>::get(this->data)
, std::forward<P>(params)...);
}
this->conditional_invoke0<F>(variant_type_list<U...>{}
, std::forward<P>(params)...);
}
To break recursive calls, introduce an overload with an empty variant_type_list:
template <template <variant_type> class F, typename... P>
void conditional_invoke0(variant_type_list<>, P&&... params) {}
When calling the invoker for the first time, provide variant_types as an argument:
this->conditional_invoke0<F>(variant_type_list<variant_type::int32, variant_type::string>{}
, std::forward<P>(params)...);
DEMO

polymorphic vector without virtual or inheritance

I am trying to implement a vector that can take elements of several types, and can apply a function on all of them. This is easily done with a base class, virtual functions and inheritance, but I explicity do not want to use it. Here is how far I am so far:
#include <iostream>
#include <vector>
#include <tuple>
// this will be my new polymorphic vector;
template<typename... Ts>
class myvector {
std::tuple<std::vector<Ts>...> vectors;
template <template<typename> class funtype>
void for_each() {
}
template <template<typename> class funtype, typename X, typename... Xs>
void for_each() {
std::vector<X>& vector = std::get<std::vector<X>>(vectors);
for ( X& x : vector ) {
funtype<X> fun;
fun(x);
}
for_each<funtype, Xs...>();
}
public:
template <typename T>
void push_back(const T& t) {
std::vector<T>& vector = std::get<std::vector<T>>(vectors);
vector.push_back(t);
}
template <typename T>
void pop_back() {
std::vector<T>& vector = std::get<std::vector<T>>(vectors);
vector.pop_back();
}
/* here I would like to pass a function, or function object that
* can be expanded to all underlying types. I would prefer to just
* give a function name, that has an implementation to all types in Ts
*/
template <template<typename> class funtype>
void ForEach() {
for_each<funtype,Ts...>();
}
};
struct foo {
};
struct bar {
};
template <typename T>
void method(T& t);
template<>
void method(foo& b) {
std::cout << "foo" << std::endl;
}
template<>
void method(bar& b) {
std::cout << "bar" << std::endl;
}
int main()
{
myvector<foo,bar> mv;
mv.push_back( foo{} );
mv.push_back( bar{} );
mv.ForEach<method>();
}
at the moment I am kind of stuck, I hope you can give me some advise on how to go further.
A common solution is to use a function object with a set of operator():
struct my_fun_type
{
void operator()(foo&) const
{ std::cout << "foo\n"; }
void operator()(bar&) const
{ std::cout << "bar\n"; }
};
This allows to pass a "set" of overloaded functions to an algorithm, state, and is rather convenient to use:
my_algorithm(my_fun_type{});
If we want to add support for such function objects, we could define ForEach as follows:
template <typename Elem, typename Fun>
void for_each(Fun&& fun) {
std::vector<Elem>& vector = std::get<std::vector<Elem>>(vectors);
for ( Elem& e : vector ) {
fun(x);
}
}
template <typename Fun>
void ForEach(Fun&& fun) {
int dummy[] = { 0, (for_each<Ts>(fun), 0)... };
(void)dummy;
}
That dummy is a trick to call for_each for all types in Ts. The (void)dummy is intended to suppress a compiler warning (dummy is never read from).
You can learn more about this technique in other Q&As, such as that one.
The Fun&& is not an rvalue reference, but a universal reference.
Note that the above example differs from many Standard Library algorithms, which take the function object by value:
template <typename Elem, typename Fun>
void for_each(Fun fun) {
std::vector<Elem>& vector = std::get<std::vector<Elem>>(vectors);
std::for_each(vector.begin(), vector.end(), std::move(fun));
}
template <typename Fun>
void ForEach(Fun fun) {
int dummy[] = { 0, (for_each<Ts>(fun), 0)... };
(void)dummy;
}
To pass a set of overloaded free functions, we can wrap them in a function object (thank #Yakk for the suggestion):
struct method_t
{
template<class... Ts>
void operator()(Ts&&... ts) const
{ method( std::forward<Ts>(ts)... ); }
};
In C++1y, such a function object type can be created with less boilerplate using a polymorphic lambda:
[](auto&&... pp)
{ method( std::forward<decltype(pp)>(pp)... ); }

c++ template specialization for all subclasses

I need to create a template function like this:
template<typename T>
void foo(T a)
{
if (T is a subclass of class Bar)
do this
else
do something else
}
I can also imagine doing it using template specialization ... but I have never seen a template specialization for all subclasses of a superclass. I don't want to repeat specialization code for each subclass
You can do what you want but not how you are trying to do it! You can use std::enable_if together with std::is_base_of:
#include <iostream>
#include <utility>
#include <type_traits>
struct Bar { virtual ~Bar() {} };
struct Foo: Bar {};
struct Faz {};
template <typename T>
typename std::enable_if<std::is_base_of<Bar, T>::value>::type
foo(char const* type, T) {
std::cout << type << " is derived from Bar\n";
}
template <typename T>
typename std::enable_if<!std::is_base_of<Bar, T>::value>::type
foo(char const* type, T) {
std::cout << type << " is NOT derived from Bar\n";
}
int main()
{
foo("Foo", Foo());
foo("Faz", Faz());
}
Since this stuff gets more wide-spread, people have discussed having some sort of static if but so far it hasn't come into existance.
Both std::enable_if and std::is_base_of (declared in <type_traits>) are new in C++2011. If you need to compile with a C++2003 compiler you can either use their implementation from Boost (you need to change the namespace to boost and include "boost/utility.hpp" and "boost/enable_if.hpp" instead of the respective standard headers). Alternatively, if you can't use Boost, both of these class template can be implemented quite easily.
I would use std::is_base_of along with local class as :
#include <type_traits> //you must include this: C++11 solution!
template<typename T>
void foo(T a)
{
struct local
{
static void do_work(T & a, std::true_type const &)
{
//T is derived from Bar
}
static void do_work(T & a, std::false_type const &)
{
//T is not derived from Bar
}
};
local::do_work(a, std::is_base_of<Bar,T>());
}
Please note that std::is_base_of derives from std::integral_constant, so an object of former type can implicitly be converted into an object of latter type, which means std::is_base_of<Bar,T>() will convert into std::true_type or std::false_type depending upon the value of T. Also note that std::true_type and std::false_type are nothing but just typedefs, defined as:
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;
I know this question has been answered but nobody mentioned that std::enable_if can be used as a second template parameter like this:
#include <type_traits>
class A {};
class B: public A {};
template<class T, typename std::enable_if<std::is_base_of<A, T>::value, int>::type = 0>
int foo(T t)
{
return 1;
}
I like this clear style:
void foo_detail(T a, const std::true_type&)
{
//do sub-class thing
}
void foo_detail(T a, const std::false_type&)
{
//do else
}
void foo(T a)
{
foo_detail(a, std::is_base_of<Bar, T>::value);
}
The problem is that indeed you cannot do something like this in C++17:
template<T>
struct convert_t {
static auto convert(T t) { /* err: no specialization */ }
}
template<T>
struct convert_t<T> {
// T should be subject to the constraint that it's a subclass of X
}
There are, however, two options to have the compiler select the correct method based on the class hierarchy involving tag dispatching and SFINAE.
Let's start with tag dispatching. The key here is that tag chosen is a pointer type. If B inherits from A, an overload with A* is selected for a value of type B*:
#include <iostream>
#include <type_traits>
struct type_to_convert {
type_to_convert(int i) : i(i) {};
type_to_convert(const type_to_convert&) = delete;
type_to_convert(type_to_convert&&) = delete;
int i;
};
struct X {
X(int i) : i(i) {};
X(const X &) = delete;
X(X &&) = delete;
public:
int i;
};
struct Y : X {
Y(int i) : X{i + 1} {}
};
struct A {};
template<typename>
static auto convert(const type_to_convert &t, int *) {
return t.i;
}
template<typename U>
static auto convert(const type_to_convert &t, X *) {
return U{t.i}; // will instantiate either X or a subtype
}
template<typename>
static auto convert(const type_to_convert &t, A *) {
return 42;
}
template<typename T /* requested type, though not necessarily gotten */>
static auto convert(const type_to_convert &t) {
return convert<T>(t, static_cast<T*>(nullptr));
}
int main() {
std::cout << convert<int>(type_to_convert{5}) << std::endl;
std::cout << convert<X>(type_to_convert{6}).i << std::endl;
std::cout << convert<Y>(type_to_convert{6}).i << std::endl;
std::cout << convert<A>(type_to_convert{-1}) << std::endl;
return 0;
}
Another option is to use SFINAE with enable_if. The key here is that while the snippet in the beginning of the question is invalid, this specialization isn't:
template<T, typename = void>
struct convert_t {
static auto convert(T t) { /* err: no specialization */ }
}
template<T>
struct convert_t<T, void> {
}
So our specializations can keep a fully generic first parameter as long we make sure only one of them is valid at any given point. For this, we need to fashion mutually exclusive conditions. Example:
template<typename T /* requested type, though not necessarily gotten */,
typename = void>
struct convert_t {
static auto convert(const type_to_convert &t) {
static_assert(!sizeof(T), "no conversion");
}
};
template<>
struct convert_t<int> {
static auto convert(const type_to_convert &t) {
return t.i;
}
};
template<typename T>
struct convert_t<T, std::enable_if_t<std::is_base_of_v<X, T>>> {
static auto convert(const type_to_convert &t) {
return T{t.i}; // will instantiate either X or a subtype
}
};
template<typename T>
struct convert_t<T, std::enable_if_t<std::is_base_of_v<A, T>>> {
static auto convert(const type_to_convert &t) {
return 42; // will instantiate either X or a subtype
}
};
template<typename T>
auto convert(const type_to_convert& t) {
return convert_t<T>::convert(t);
}
Note: the specific example in the text of the question can be solved with constexpr, though:
template<typename T>
void foo(T a) {
if constexpr(std::is_base_of_v<Bar, T>)
// do this
else
// do something else
}
If you are allowed to use C++20 concepts, all this becomes almost trivial:
template<typename T> concept IsChildOfX = std::is_base_of<X, T>::value;
// then...
template<IsChildOfX X>
void somefunc( X& x ) {...}