Enable Template Ctor based on template parameter - c++

I got a simple CRTP template class that I've been playing with and I came up to a point that I need to have the same ctor but essentially do different things at the constructor initialization list.
I do have an alternative solution but I was wondering if what I wanted to do could be achieved in a better way.
A strip down version of the class is pasted below.
You can ignore the Eigen stuff its just a matrix library.
template<class ImplT, size_t N, bool SquareMatrix = false>
class Foo
{
template<bool Test, typename Then, typename Else>
using conditional_t = typename std::conditional<Test, Then, Else>::type;
// this is defined at compile time so no need to init it
using VecN = Eigen::Matrix<double, N, 1>;
// this is a dynamic matrix so dimensions need to be specified at runtime
using MatN = Eigen::MatrixXd;
using MatrixType = conditional_t<SquarePreCovariance, MatN, VecN>;
inline ImplT& impl() noexcept { return static_cast<ImplT&>(*this); }
inline const ImplT& impl() const noexcept { return static_cast<const ImplT&>(*this); }
public:
// if SquareMatrix == false
Foo()
: parameters3(Matrix(N, N))
{}
// if SquareMatrix == true
Foo()
: parameters2(Matrix(N, N)),
parameters3(Matrix(N, N)),
{}
// easy solution that covers all cases
Foo()
: parameters3(Matrix(N, N)),
{
if (SquareMatrix)
parameters2.resize(N,N);
}
private:
VecN parameters;
MatrixType parameters2;
MatN parameters3;
};

What about conditionally delegating constructors?
private:
// if SquareMatrix == false
Foo(std::false_type)
: parameters3(Matrix(N, N))
{}
// if SquareMatrix == true
Foo(std::true_type)
: parameters2(Matrix(N, N)),
parameters3(Matrix(N, N))
{}
public:
Foo() : Foo(std::integral_constant<bool, SquareMatrix>{}) {}

Constructors can be templated, so long as all template arguments can be deduced or have defaults.
Substitution failures for template arguments cause it to be skipped during overload resolution.
Care must be taken to make it clear that the two constructor versions are valid overloads, and an additional dummy template parameter can take care of that.
#include <utility>
#include <iostream>
template <bool B>
struct S {
template <bool B_ = B, typename = typename std::enable_if<!B_>::type>
S() { std::cout << "1" << std::endl; }
template <bool B_ = B, typename = typename std::enable_if<B_>::type, typename = void>
S() { std::cout << "2" << std::endl; }
};
int main() {
S<false> sf; // okay, prints 1
S<true> st; // okay, prints 2
}

Related

Overloading and template specialization for function call priority

With reference to the following code:
I am trying to conditionally compile a bunch of functions and then 'order' them using the prioirty_tag class. My question is, if I replace enable_if_t<is_nothrow_move_constructible<U>{}>* = nullptr> with enable_if_t<is_nothrow_move_constructible<U>{}>> the output is incorrect (defaults to the first function).
What exactly is happening there? why does adding the * = nullptr make it work?
#include <iostream>
#include <type_traits>
using namespace std;
template <size_t T>
struct priority_tag: priority_tag<T-1> {};
template <>
struct priority_tag<0> {};
template <typename T>
struct my_vec
{
template <typename U = T, typename = void>
void realloc_impl(priority_tag<0> pr)
{
cout << "Move throw construct\n";
};
//template <typename U = T, enable_if_t<is_copy_constructible<U>{}>> this wont work!
template <typename U = T, enable_if_t<is_copy_constructible<U>{}>* = nullptr>
void realloc_impl(priority_tag<1> pr)
{
cout << "copy construct \n";
};
//template <typename U = T, enable_if_t<is_copy_constructible<U>{}>> this wont work!
template <typename U = T, enable_if_t<is_nothrow_move_constructible<U>{}>* = nullptr>
void realloc_impl(priority_tag<2> pr)
{
cout << "nothrow move \n";
};
void realloc()
{
priority_tag<2> pr;
realloc_impl(pr);
}
const static int val = is_nothrow_move_constructible<T>{} ? 1 : is_copy_constructible<T>{} ? 2 : 3;
priority_tag<val> g;
};
class A {
public:
A() = default;
A(A&&) noexcept = default;
};
class B {
public:
B() = default;
B(B&&) = delete;
B(const B&) = default;
};
class C {
public:
C() = default;
C(C&&) {}
C(const C&) = delete;
};
int main()
{
my_vec<A> obj;
obj.realloc();
cout << obj.val;
}
Try to compile below code
template<void>
void foo(){}
I got compiler error 'void' is not a valid type for a template non-type parameter.
As template parameter you can pass:
1) type
then you declare it using class/typename as below:
template< class/typename A[optional] = void>
void foo2(){}
2) non-type
then you can pass as template parameter some intergers value, pointers, Lvalue reference, etc (full list here)
template<void*>
void foo3(){}
3) template type parameter
In your example
is_nothrow_move_constructible
returns true for A, then compiler meets line:
template <typename U = T, enable_if_t<is_nothrow_move_constructible<U>{}>>
what is:
template <typename U = T, void>
this line has incorrect syntax, and compiler removes this member function template from overloads set.
You can fix it by declaring enable_if_t<is_nothrow_move_constructible<U>{} as type parameter:
template <typename U = T,
typename = enable_if_t<is_nothrow_move_constructible<U>{}> > // typename = void
void realloc_impl(priority_tag<2> pr)
{
cout << "nothrow move \n";
};
or as non-type (pointer to void), what you did in your example.

Define a constructor for a class whose members exist according to the template base classes

I'm working on a class to identify an image inside a texture, something like this:
using level_t = ...
using layer_t = ...
using face_t = ...
template <bool>
struct levels {};
template <>
struct levels<true> {
level_t level = level_t{0};
};
template <bool>
struct layers {};
template <>
struct layers<true> {
layer_t layer = layer_t{0};
};
template <bool>
struct faces {};
template <>
struct faces<true> {
face_t face = face_t{0};
};
template <bool HasLevels, bool HasLayers, bool HasFaces>
struct index : levels<HasLevels>, layers<HasLayers>, faces<HasFaces> {};
Where level_t, layer_t and face_t are a sort of "strong typedef" for an int (basically different types that look like an int but without the implicit conversions).
Now I can use my type like so:
using Index1 = index<true, true, false>;
void f(Index1);
Index1 v;
v.level = level_t{1};
v.layer = layer_t[1};
f(v);
But to make the life easier for my users I want to allow this code:
f({level_t{1}, layer_t{1}};
The aggregate initialization is out due the base classes, so I need to write a constructor on my own; what is the best/smartest way to write a constructor that supports all the argument combinations (but not the reorder)?
The error messages are a bit terrible if you pass the wrong types or number of arguments, so I'm not sure this is worth it, but…
One thing needed is a way to filter and randomly-access a pack of types; realistically I would use Brigand or Boost.Hana for this, but I'll stick with std::tuple<> here to keep this standard:
namespace detail {
template<template<bool> class Holder, bool B>
std::integral_constant<bool, B> holder_value_(Holder<B>);
template<typename HolderT>
constexpr decltype(detail::holder_value_(std::declval<HolderT>())) holder_value() {
return {};
}
template<typename UnfilteredT, typename FilteredT>
struct filter_holders;
template<typename UnfilteredT, typename FilteredT = std::tuple<>>
using filter_holders_t = typename filter_holders<UnfilteredT, FilteredT>::type;
template<typename... Fs>
struct filter_holders<std::tuple<>, std::tuple<Fs...>> {
using type = std::tuple<Fs...>;
};
template<typename U, typename... Us, typename... Fs>
struct filter_holders<std::tuple<U, Us...>, std::tuple<Fs...>> : filter_holders<
std::tuple<Us...>,
std::conditional_t<holder_value<U>(), std::tuple<Fs..., U>, std::tuple<Fs...>>
> { };
}
Another thing needed is a way to find what intlike-type a 'holder'-type is wrapping (e.g. that levels<true> wraps a level_t, etc.). This can be done non-intrusively, but here I'll assume an internal value_type typedef.
With those in place it's fairly straightforward – we get a pack of base types for which the template argument is true and write a constructor whose initializers are just an expansion of that pack:
namespace detail {
template<typename... HolderTs>
std::tuple<typename HolderTs::value_type...> value_types_(std::tuple<HolderTs...>);
template<typename HoldersT>
using value_types_t = decltype(detail::value_types_(std::declval<HoldersT>()));
}
template<bool HasLevels, bool HasLayers, bool HasFaces>
struct index : levels<HasLevels>, layers<HasLayers>, faces<HasFaces> {
private:
using bases_t = std::tuple<levels<HasLevels>, layers<HasLayers>, faces<HasFaces>>;
using true_bases_t = detail::filter_holders_t<bases_t>;
using arg_types_t = detail::value_types_t<true_bases_t>;
template<std::size_t... Is, typename... ArgTs>
index(std::index_sequence<Is...>, ArgTs&&... args)
: std::tuple_element_t<Is, true_bases_t>{std::forward<ArgTs>(args)}... { }
public:
index() = default;
template<
typename... ArgTs,
std::size_t S = sizeof...(ArgTs),
typename = std::enable_if_t<
S && std::is_same<std::tuple<std::decay_t<ArgTs>...>, arg_types_t>{}
>
>
index(ArgTs&&... args)
: index{std::make_index_sequence<S>{}, std::forward<ArgTs>(args)...} { }
};
Online Demo
EDIT: Updated with stricter implementation that doesn't allow convertible-but-not-same argument types, and doesn't incorrectly interfere with copy/move-construction.
Here is a demo code which allowed the code.
#include <iostream>
#include <type_traits>
struct A {
A() = default;
A(const A &) {
std::cout << "A" << std::endl;
}
};
struct B {
B() = default;
B(const B &) {
std::cout << "B" << std::endl;
}
};
struct C {
C() = default;
C(const C &) {
std::cout << "C" << std::endl;
}
};
template <class... Base>
struct Hyper : public Base... {
template <class... Args>
Hyper(Args&&... args) : Base(std::forward<Args>(args))... {}
};
template <class... Base>
auto factory(Base&&... args) {
return Hyper<std::decay_t<Base>...>(std::forward<Base>(args)...);
}
int main() {
auto obj = factory(A{}, B{}, C{});
return 0;
}
All base classes of Hyper are copy/move constructed.
Some static_assert can be added to factory to prevent it from generating any freaky class.
As the comment says, this is not a good idea. If you do it right, everything will be fine, but if you do it wrong, even a small mistake, template + multi-inheritance can be your nightmare. (You may end up with 100000 lines of compiler error message.)

C++ passing parameter pack to class

I'd like to have a class that gets in it's Ctor unlimited parameters of the same type, and stores them into a vector. It should look like that:
class A(int a, int b, **N time parameter of type T**)
: data(**vector will get N times type T**)
{
}
protected:
vector<T> data;
How should I implement it? Solution could be in c++11/14
I got a few errors such as "parameter packs not expanded with ‘…' ", etc..
This code sample might be useful:
#include <vector>
#include <utility>
template<typename T>
class MyClass {
public:
template<typename ...Args>
MyClass(int a, int b, Args&& ...args) :data{ std::forward<Args>(args)... } {}
private:
std::vector<T> data;
};
int main() {
MyClass<char> sample(1, 2, 'a', 'b');
return 0;
}
[EDIT]: Added std::forward, added missing include for utility
Assuming that T could be anything, even something quite large or non-copyable, we'd want to:
preserve efficiency with perfect forwarding.
check types.
std::initializer_list satisfies 2 but not 1.
Simple variadic template expansion satisfies 1 and not 2.
This solution uses variadic template expansion and enable_if to enforce type compatibility.
#include <vector>
#include <utility>
#include <string>
namespace detail
{
constexpr bool all()
{
return true;
}
template<class...Rest>
constexpr bool all(bool b, Rest...rest)
{
return b and all(rest...);
};
}
template<class T>
class A
{
public:
using value_type = T; // say
template<class...Rest,
std::enable_if_t<detail::all(std::is_convertible<Rest, value_type>::value...)>* = nullptr>
A(int a, int b, Rest&&...rest)
: a_(a), b_(b)
{
this->fill(std::forward_as_tuple(std::forward<Rest>(rest)...),
std::make_index_sequence<sizeof...(Rest)>());
}
private:
template<class Tuple, std::size_t...Is>
void fill(Tuple&& t, std::index_sequence<Is...> seq)
{
data_.reserve(seq.size());
using expand = int[];
void(expand{ 0,
(data_.push_back(std::move(std::get<Is>(t))), 0)...
});
}
private:
int a_, b_;
std::vector<value_type> data_;
};
int main()
{
using namespace std::literals;
auto a = A<double>(1, 2, 4.3, 5.5, 6.6);
auto b = A<std::string>(1, 2, "the", "cat"s, "sat on the mat");
// error: no matching constructor...
// auto err = A<std::string>(1, 2, "the", "cat"s, 0.1);
}
Here you go:
#include <iostream>
#include <vector>
template<class T>
struct V
{
V(int n, std::initializer_list<T> l)
: data(l)
{
(void) n;
}
std::vector<T> data;
};
int main()
{
V<int> v(0,{1,2,3});
}
This is not a perfect example since one needs to construct an object with the weird syntax (n, {optional, arguments, of, same, type}) but it does provide wanted behavior.
The following example is similar to fr3nzy90's, but with the coming C++17 it will allow automatic deduction of T from the constructor arguments:
template <class T>
class MyContainer {
private:
std::vector<T> data;
public:
// Take the first T value explicitly so it can be used to deduce
// T from the constructor arguments (C++17 feature).
template <class... Ts>
MyContainer(int a, int b, T const & tval, Ts const &... tvals) :
data{tval, tvals...} {
…
}
// Special case, empty list, no implicit type deduction, because
// there is no T value to deduce it from.
MyContainer(int a, int b) {
…
}
};

How to check if a class has a default constructor, either public, protected or private

I need to check if a class C has a default constructor, either implicit or custom, and either public, protected or private.
I tried using std::is_default_constructible<C>::value, which returns true if C has a public default constructor (either implicit or custom) but false if C has a protected or private default constructor (seams to be the standard behavior though.)
Is there any way to check if a class has a protected or private default constructor ?
Note (if this may help): the check is performed from a function that is friend of the class C to be checked.
I need to perform this check in order to default-construct objects corresponding to the nullptr pointers of the m_objs tuple, member of a Foo object (partial Foo definition below):
template<class... Objects>
class Foo
{
public:
Foo(Objects*... objects)
: m_objs(objects...)
{
// User construct a Foo objects passing a pack of pointers
// some of them are nullptr, some are not.
// The following call should default-construct objects corresponding
// to the null pointers of m_objs member tuple:
objs_ctor<Objects...>();
}
private:
template<class Obj, class O1, class ...On>
void objs_ctor()
{
objs_ctor<Obj>(); objs_ctor<O1, On...>();
}
template<class Obj>
typename std::enable_if<std::is_default_constructible<Obj>::value, void>::type
objs_ctor()
{
Obj*& obj = std::get<Obj*>(m_objs);
if (obj == nullptr)
obj = new Obj; // default-construct Obj
}
template<class Obj>
typename std::enable_if<!std::is_default_constructible<Obj>::value, void>::type
objs_ctor()
{
Obj*& obj = std::get<Obj*>(m_objs);
assert(obj != nullptr); // terminate if not pre-constructed
}
private:
std::tuple<Objects*...> m_objs;
};
which is intended to be used as:
struct A { };
class B {
B() = default;
template <class... Ts>
friend class Foo;
};
int main() {
// currently asserts, even though Foo<A,B> is a friend of B
// and B is default-constructible to its friends
Foo<A, B> foo(nullptr, nullptr);
}
Example above asserts because std::is_default_constructible<B>::value is false, even though B has a [private] default ctor and Foo<A,B> is friend of B.
I will present a simplified example to make things easier. Then you can adapt it to your Foos class. The idea is to specialise my templated friend class as follows
#include <iostream>
// forward declaration
template <class... T>
struct Friend;
struct testing_tag;
// specialisation simply to check if default constructible
template <class T>
struct Friend<T, testing_tag> {
// sfinae trick has to be nested in the Friend class
// this candidate will be ignored if X does not have a default constructor
template <class X, class = decltype(X())>
static std::true_type test(X*);
template <class X>
static std::false_type test(...);
static constexpr bool value = decltype(test<T>(0))::value;
};
Notice that the std::true_type candidate will always be valid as long as X has a default constructor regardless if it is private, protected or public. Now test it
class default_public {
template <class... T>
friend struct Friend;
};
class default_protected {
template <class... T>
friend struct Friend;
protected:
default_protected() {}
};
class default_private {
template <class... T>
friend struct Friend;
private:
default_private() {}
};
class no_default {
public:
no_default(int x){}
};
// a convenient name to test with
template <class T>
using has_any_default_constructor = Friend<T, testing_tag>;
int main() {
std::cout << has_any_default_constructor<default_public>::value << std::endl;
std::cout << has_any_default_constructor<default_protected>::value << std::endl;
std::cout << has_any_default_constructor<default_private>::value << std::endl;
std::cout << has_any_default_constructor<no_default>::value << std::endl;
}
The problem is that if a class has neither public, nor protected nor private default constructor, the simple default definition of an instance gives a compilation error, not a run-time exception. So the test is simple: if this expression in a friend function C c;compiles, the class has a default ctor, either public, protected or private.
Just see example code:
#include <iostream>
#include <string>
class Cwith {
private:
std::string name;
Cwith(): name("default ctor") {}
friend void build();
};
class Cwithout {
private:
std::string name;
Cwithout(const std::string& name): name(name) {};
friend void build();
};
void build() {
Cwith cw;
std::cout << cw.name << std::endl;
Cwithout cwo; // error : class Cwithout has no defaut constructor
std::cout << cwo.name << std::endl;
}
int main() {
build();
return 0;
}
But I could not imagine a run-time test...
My understanding is that you want to check if your friend can default construct a class. The trick is that you have to have SFINAE inside the scope of the friend function. To do that, you need templates. To have local templates, you need generic lambdas.
We start with an overloader that we can pass multiple lambdas into. There may be a better way to write this:
template <class... Fs>
struct overload {
void operator()();
};
template <class F, class... Fs>
struct overload<F, Fs...> : F, overload<Fs...> {
overload(F&& f, Fs&&... fs)
: F(std::forward<F>(f))
, overload<Fs...>(std::forward<Fs>(fs)...)
{ }
using F::operator();
using overload<Fs...>::operator();
};
template <class... Fs>
overload<std::decay_t<Fs>...> make_overload(Fs&&... fs) {
return {std::forward<Fs>(fs)...};
}
Then you pass in overloaded, generic, SFINAE-d lambdas (C++14 required). Let's say we have:
struct Q {
friend void foo();
};
template <class T>
struct tag_t { using type = T; };
template <class T>
constexpr tag_t<T> tag{};
Then we could write:
void foo() {
auto f = make_overload(
[](auto x) -> decltype( typename decltype(x)::type(), void() ) {
Q q;
std::cout << "I can do it.\n";
},
[](...) {
std::cout << "What do you want here?\n";
});
f(tag<Q>, 0);
}
If works is a friend, the first overload is viable and preferred - so you get that, which constructs a Q because it can. If works is not a friend, the first overload is not viable and so you get the second overload.
The key is that we test the construction of Q (the typename decltype(x)::type() part) within works() - so it will either be covered by friendship.
So for your specific usage, that would be:
template <class Obj>
objs_ctor() {
Obj*& obj = std::get<Obj*>(m_objs);
auto ctor = make_overload(
[&](auto x) -> decltype( typename decltype(x)::type(), void() ) {
if (!obj) {
obj = new Obj;
}
},
[=](...) {
assert(obj);
});
ctor(tag<Obj>);
}

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 ) {...}