Assume we have some templated struct and sometimes it's template should be an array. How to initialize array in struct?
This
template<typename T>
struct A {
T x;
A(T x) : x(x) {}
};
int a[6];
A<decltype(a)> b(a);
generates error during compilation:
error: array initializer must be an initializer list
A(T x) : x(x) {}
^
UPD1. More complete code this thing is used in:
template<typename T>
struct A {
T x;
A(const T& x) : x(x) {}
A(const T&& x) : x(std::move(x)) {}
};
template<typename T>
A<typename std::remove_reference<T>::type> make_A(T&& a) {
return A<typename std::remove_reference<T>::type>(std::forward<T>(a));
}
auto a = make_A("abacaba");
A general solution is to provide a special constructor for arrays (enabled when T is an array) which copies the source array to the struct's array. It works, but discard move semantics for arrays.
#include <iostream>
#include <type_traits>
#include <string>
#include <tuple>
template<typename T>
struct A {
using value_type = std::remove_const_t<T>;
value_type x;
template<class U=T> A(const T& src, std::enable_if_t<!std::is_array_v<U>, int> = 0) : x(src) {}
template<class U=T> A(const T&& src, std::enable_if_t<!std::is_array_v<U>, int> = 0) : x(std::move(src)) {}
template<class U=T> A(const T& src, std::enable_if_t< std::is_array_v<U>, int> = 0) { std::copy(std::begin(src), std::end(src), std::begin(x)); }
};
template<typename T>
auto make_A(T&& a)
{ return A<typename std::remove_reference_t<T>>(std::forward<T>(a)); }
int main()
{
auto a1 = make_A("the answer");
std::ignore = a1;
auto a2 = make_A(42);
std::ignore = a2;
}
live demo
If you need T to be const for non-arrays sometimes, an improvement would be to define value_type as T if T is not an array and to std::remove_const_t<T> otherwise.
I suggest putting all the smarts into make_A, converting C-arrays to std::array<>s so that A<> needs only work with regular types:
namespace detail {
template<typename T, std::size_t... Is>
constexpr std::array<T, sizeof...(Is)> to_std_array(T const* const p,
std::index_sequence<Is...>)
{
return {{p[Is]...}};
}
}
template<typename T>
A<std::decay_t<T>> make_A(T&& x) {
return {std::forward<T>(x)};
}
template<typename T, std::size_t N>
A<std::array<T, N>> make_A(T const (& x)[N]) {
return {detail::to_std_array(x, std::make_index_sequence<N>{})};
}
Online Demo
If you're only concerned with hardcoded C-strings in particular (as opposed to C-arrays in general), consider converting to a string_view type rather than std::array<> to potentially save some space.
If it is a special behaviour you want to achieve for C-Strings exclusively, you may just add a special treatment:
// for all non-C-string cases
template<typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, const char*>>* = nullptr>
A<typename std::remove_reference<T>::type> make_A(T&& a) {
return A<typename std::remove_reference<T>::type>(std::forward<T>(a));
}
// in case a C-string got passed
A<std::string> make_A(const std::string& str) {
return A<std::string>(str);
}
int main()
{
auto a = make_A("abacaba");
auto b = make_A(5);
}
With std::decay it works:
template<typename T>
A<typename std::decay<T>::type> make_A(T&& a) {
return A<typename std::decay<T>::type>(std::forward<T>(a));
}
Related
I want to store multiple non-movable types in a single variable.
At the very first, I have tried std::tuple at the very first, but it fails.
#include <tuple>
template<typename T>
struct No {
No(T){}
No(const No &) = delete;
No(No &&) = delete;
};
struct Handmade {
No<int> a;
No<double> b;
No<char> c;
};
template<typename T>
auto no() -> No<T> { return No<T>(T()); }
auto main() -> int
{
Handmade h = {no<int>(), no<double>(), no<char>()}; // good
auto tuple = std::make_tuple(no<int>(), no<double>(), no<char>()); // fails
return 0;
}
Here, Handmade type can be initialized through aggregate initialization.
However, std::tuple is not aggregate-initialzable, it doesn't work.
Since it should be variadic, I cannot write such type Handmade for my purpose.
Is it possible to implement such variadic tepmlate data structure in current C++ standard or is there any workaround?
Yes, you can write your own aggregate tuple like this:
template <int I, typename T>
struct MyTupleElem
{
T value{};
template <int J> requires(I == J) T &get() {return value;}
template <int J> requires(I == J) const T &get() const {return value;}
};
template <typename T, typename ...P>
struct MyTupleHelper;
template <int ...I, typename ...P>
struct MyTupleHelper<std::integer_sequence<int, I...>, P...>
: MyTupleElem<I, P>...
{
using MyTupleElem<I, P>::get...;
};
template <typename ...P>
struct MyTuple : MyTupleHelper<std::make_integer_sequence<int, sizeof...(P)>, P...> {};
template <typename ...P>
MyTuple(P &&...) -> MyTuple<std::decay_t<P>...>;
template <typename T>
struct No
{
T value;
No(T value) : value(value) {}
No(const No &) = delete;
No(No &&) = delete;
};
template <typename T>
No<T> no(T t) {return No<T>(t);}
int main()
{
MyTuple h = {no<int>(1), no<double>(2.3), no<char>('4')};
std::cout << h.get<0>().value << '\n';
std::cout << h.get<1>().value << '\n';
std::cout << h.get<2>().value << '\n';
}
And I think it's a good way to make tuples in general, even if you don't want aggregate-ness. Last time I tested, such tuples could tolerate more elements than the classic ones with the bases chained on top of each other.
I am implementing my static multi-dimentional vector class. I am using std::array as the underlying data type.
template <typename T, std::size_t N>
class Vector {
private:
std::array<T, N> data;
};
I want to make my class downwards-compatible, so I am writing this:
template <typename T, std::size_t N>
class Vector : public Vector<T, N-1>{
private:
std::array<T, N> data;
};
template <typename T>
class Vector<T, 0> {};
My goal is that when one instance is used in downwards-compatible mode, its underlying data should be able to be reliably accessed:
template<typename T, std::size_t N>
T& Vector<T, N>::operator[](int i) {
// Do boundary checking here
return this->data[i];
}
void foo(Vector<int, 3>& arg) {
arg[1] = 10;
}
Vector<int, 5> b;
foo(b);
// Now b[1] should be 10
There are two points here:
Vector<T, 5> should be accepted by foo(), Vector<T, 2> should be rejected.
Changes to b[0] through b[2] in foo() should pertain. b[3] and b[4] should not be accessible in foo().
How can I achieve that?
How about a simple read wrapper around std::array<> itself?
template<typename T, std::size_t N>
struct ArrayReader {
public:
// Intentionally implicit.
template<std::size_t SRC_LEN>
ArrayReader(std::array<T, SRC_LEN> const& src)
: data_(src.data()) {
static_assert(SRC_LEN >= N);
}
private:
T const* data_;
};
void foo(ArrayReader<float, 3>);
void bar() {
std::array<float, 4> a;
std::array<float, 2> b;
foo(a);
foo(b); //BOOM!
}
Of course, you can easily substitute std::array for your own type, this is just an example of the principle.
Have array keep the data, and then create additional non-owning class, e.g. array_view that will keep only a pointer. It will have generic constructor that accepts the array, and will have a static_assert to check the sizes.
Here's how I would approach this:
template <class Container, std::size_t size>
struct range_view
{
range_view(Container * p): container(p) { assert(size <= p->size()); }
auto & operator[](std::size_t i) { return (*container)[i]; }
private:
Container * container;
};
Then you simply define foo as:
template <class C>
void foo(range_view<C, 3> c)
{
c[1] = 1;
}
Here's something that is closest to what I think you would need.
Make Vector a viewer/user of the data, not the owner of the data.
#include <array>
template <typename T, std::size_t N, std::size_t I>
class Vector : public Vector<T, N, I-1>
{
public:
Vector(std::array<T, N>& arr) : Vector<T, N, I-1>(arr), arr_(arr) {}
T& operator[](int i) {
return arr_[i];
}
private:
std::array<T, N>& arr_;
};
template <typename T, std::size_t N>
class Vector<T, N, 0ul>
{
public:
Vector(std::array<T, N>& arr) : arr_(arr) {}
private:
std::array<T, N>& arr_;
};
void foo(Vector<int, 5, 3>& arg) {
arg[1] = 10;
// Can't find a way to make this a compile time error.
arg[3] = 10;
}
#include <iostream>
int main()
{
std::array<int, 5> arr;
Vector<int, 5, 5> b(arr);
foo(b);
std::cout << b[1] << std::endl;
}
Here's a demonstration of how to implement the Vector class that you tried in your question. At each level you only store 1 value instead of an array and that way when you compose all your N Arrays together you get space for N values. Of course then calling operator[] gets tricky, which is the meat of what I wanted to demonstrate.
#include <utility>
template <class T, std::size_t N>
struct Array : Array<T, N-1>
{
T & operator[](std::size_t i)
{
return const_cast<T&>((*const_cast<const Array*>(this))[i]);
}
const T & operator[](std::size_t i) const
{
return Get(i, std::make_index_sequence<N>());
}
template <std::size_t i>
const T & Geti() const
{
return static_cast<const Array<T, i+1>&>(*this).GetValue();
}
const T & GetValue() const { return value; }
template <std::size_t ... indices>
const T & Get(std::size_t i, std::integer_sequence<std::size_t, indices...>) const
{
using X = decltype(&Array::Geti<0>);
X getters[] = { &Array::Geti<indices>... };
return (this->*getters[i])();
}
template <std::size_t i, class = typename std::enable_if<(i <= N)>::type>
operator Array<T, i>&() { return (Array<T, i>&)*this; }
private:
T value;
};
template <class T>
struct Array<T, 0>{};
void foo(Array<float, 3> & a) { a[1] = 10; }
int main()
{
Array<float, 10> a;
foo(a);
}
In C++11, is there a DRY way to construct all elements of an array with some same set of parameters for all elements? (e.g. via a single initializer list?)
For example:
class C {
public:
C() : C(0) {}
C(int x) : m_x{x} {}
int m_x;
};
// This would construct just the first object with a parameter of 1.
// For the second and third object the default ctor will be called.
C ar[3] {1};
// This would work but isn't DRY (in case I know I want all the elements in the array to be initialized with the same value.
C ar2[3] {1, 1, 1};
// This is DRYer but obviously still has repetition.
const int initVal = 1;
C ar3[3] {initVal, initVal, initVal};
I know my goal is easily achievable by using an std::vector. I'm wondering if it's possible with raw arrays as well.
c++14 - a little work will make this work for c++11
#include <iostream>
#include <array>
#include <utility>
class C {
public:
C() : C(0) {}
C(int x) : m_x{x} {}
int m_x;
};
namespace detail {
template<class Type, std::size_t...Is, class...Args>
auto generate_n_with(std::index_sequence<Is...>, const Args&...args)
{
return std::array<Type, sizeof...(Is)> {
{(void(Is), Type { args... })...} // Or replace '{ args... }' with '( args... )'; see in comments below.
};
}
}
template<class Type, std::size_t N, class...Args>
auto generate_n_with(const Args&...args)
{
return detail::generate_n_with<Type>(std::make_index_sequence<N>(), args...);
}
int main()
{
auto a = generate_n_with<C, 3>(1);
for (auto&& c : a)
{
std::cout << c.m_x << std::endl;
}
}
results:
1
1
1
I want to guarantee no copies prior to c++17
The you would need to generate into a vector:
template<class Container, class...Args>
auto emplace_n(Container& c, std::size_t n, Args const&...args)
{
c.reserve(n);
while(n--) {
c.emplace_back(args...);
}
};
used like this:
std::vector<C> v2;
emplace_n(v2, 3, 1);
You can construct a sequence of elements using an std::index_sequence<...> and expand that into the initializers of an array. I don't know of any approach avoiding an auxiliary function, though. Here is an example:
#include <iterator>
#include <algorithm>
#include <iostream>
struct S {
int value;
S(int value): value(value) {}
};
std::ostream& operator<< (std::ostream& out, S const& s) {
return out << s.value;
}
#include <array>
#include <iterator>
#include <algorithm>
#include <iostream>
struct S {
int value;
S(int value): value(value) {}
};
std::ostream& operator<< (std::ostream& out, S const& s) {
return out << s.value;
}
template <typename T, std::size_t... I>
std::array<T, sizeof...(I)> fill_aux(T value, std::index_sequence<I...>)
{
return std::array<T, sizeof...(I)>{ (void(I), value)... };
}
template <std::size_t N, typename T>
std::array<T, N> fill(T value) {
return fill_aux(value, std::make_index_sequence<N>());
}
int main()
{
std::array<S, 10> array = fill<10>(S(17));
std::copy(array.begin(), array.end(), std::ostream_iterator<S>(std::cout, " "));
}
By creating derived class, you can effectively create a new default value. It's a bit hackish, but may be less hackish than other solutions. Here's an example:
class C {
public:
C() : C(0) {}
C(int x) : m_x{x} {}
int m_x;
};
template <int init>
struct CInit : C { CInit() : C(init) {} };
CInit<1> ar2[3];
const int initVal = 1;
CInit<initVal> ar3[3];
Another approach is to wrap your raw array inside a struct with a variadic constructor:
template <size_t n>
struct Array {
C array[n];
template <size_t... seq>
Array(int init,std::index_sequence<seq...>)
: array{(void(seq),init)...}
{
}
Array(int init)
: Array(init,std::make_index_sequence<n>())
{
}
};
const int initVal = 1;
Array<3> ar3_1(initVal);
const C (&ar3)[3] = ar3_1.array;
Building on Richard's answer, it's also possible to define
template<class Type, std::size_t N, class...Args>
auto generate_n_with(const std::array<Type, N>&, const Args&...args)
{
return detail::generate_n_with<Type>(std::make_index_sequence<N>(), args...);
};
Allowing you to enter the array as a parameter to make the code more dry in case you already know the type of the array, e.g.
class D {
public:
D();
std::array<int, 3> m_ar;
};
Allowing
D::D() : m_ar{generate_n_with{m_ar, 5}} {}
Instead of the less DRY
D::D() : m_ar{generate_n_with<int, 3>{5}} {}
P.S. maybe there's an even DRYer way without repeating m_ar twice?
If I have a struct like:
struct Thing
{
int x;
int y;
bool a;
bool b;
}
Then I can create a Thing object by doing: Thing t {1,2,true,false};. However, if I have a tuple then I am doing something like:
std::tuple<int, int, bool, bool> info = std::make_tuple(1,2,true,false);
Thing t { std::get<0>(info), std::get<1>(info).. // and so on
Is there a better way to do this?
We can create a generic factory function for creating aggregates from tuple-like types (std::tuple, std::pair, std::array, and arbitrary user-defined tuple-like objects in a structured bindings world†):
template <class T, class Tuple, size_t... Is>
T construct_from_tuple(Tuple&& tuple, std::index_sequence<Is...> ) {
return T{std::get<Is>(std::forward<Tuple>(tuple))...};
}
template <class T, class Tuple>
T construct_from_tuple(Tuple&& tuple) {
return construct_from_tuple<T>(std::forward<Tuple>(tuple),
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}
);
}
which in your case would be used as:
std::tuple<int, int, bool, bool> info = std::make_tuple(1,2,true,false);
Thing t = construct_from_tuple<Thing>(info); // or std::move(info)
This way, Thing can still be an aggregate (don't have to add constructor/assignments), and our solution solves the problem for many, many types.
As an improvement, we could add SFINAE to both overloads to ensure that they're not callable with invalid tuple types.
†Pending accepting wording of how decomposition will work, the qualified call to std::get<Is> may need to be changed to an unqualified call to get<Is> which has special lookup rules. For now, this is moot, since it's 2016 and we don't have structured bindings.
Update: In C++17, there is std::make_from_tuple().
If you are using c++14 you could make use of std::index_sequence creating helper function and struct as follows:
#include <tuple>
#include <utility>
struct Thing
{
int x;
int y;
bool a;
bool b;
};
template <class Thi, class Tup, class I = std::make_index_sequence<std::tuple_size<Tup>::value>>
struct Creator;
template <class Thi, class Tup, size_t... Is>
struct Creator<Thi, Tup, std::index_sequence<Is...> > {
static Thi create(const Tup &t) {
return {std::get<Is>(t)...};
}
};
template <class Thi, class Tup>
Thi create(const Tup &t) {
return Creator<Thi, Tup>::create(t);
}
int main() {
Thing thi = create<Thing>(std::make_tuple(1,2,true,false));
}
And the version without additional class (with one additional function):
#include <tuple>
#include <utility>
struct Thing
{
int x;
int y;
bool a;
bool b;
};
template <class Thi, class Tup, size_t... Is>
Thi create_impl(const Tup &t, std::index_sequence<Is...>) {
return {std::get<Is>(t)...};
}
template <class Thi, class Tup>
Thi create(const Tup &t) {
return create_impl<Thi, Tup>(t, std::make_index_sequence<std::tuple_size<Tup>::value>{});
}
int main() {
Thing thi = create<Thing>(std::make_tuple(1,2,true,false));
}
Yet another this time tricky version with just one helper function:
#include <tuple>
#include <utility>
struct Thing
{
int x;
int y;
bool a;
bool b;
};
template <class R, class T, size_t... Is>
R create(const T &t, std::index_sequence<Is...> = {}) {
if (std::tuple_size<T>::value == sizeof...(Is)) {
return {std::get<Is>(t)...};
}
return create<R>(t, std::make_index_sequence<std::tuple_size<T>::value>{});
}
int main() {
Thing thi = create<Thing>(std::make_tuple(1,2,true,false));
}
You may use std::tie:
Thing t;
std::tie(t.x, t.y, t.a, t.b) = info;
Here are other ways:
struct Thing
{
Thing(){}
Thing(int A_, int B_, int C_, int D_) //1
: A(A_), B(B_), C(C_), D(D_) {}
Thing(std::tuple<int,int,bool,bool> tuple) //3
: A(std::get<0>(tuple)), B(std::get<1>(tuple)),
C(std::get<2>(tuple)), D(std::get<3>(tuple)) {}
void tie_from_tuple(std::tuple<int,int,bool,bool> tuple) //4
{
std::tie(A,B,C,D) = tuple;
}
int A;
int B;
bool C;
bool D;
};
inline Thing tuple_to_thing(const std::tuple<int,int,bool,bool>& tuple) //2
{
return Thing{std::get<0>(tuple), std::get<1>(tuple),
std::get<2>(tuple), std::get<3>(tuple)};
}
int main()
{
auto info = std::make_tuple(1,2,true,false);
//1 make a constructor
Thing one(info);
//2 make a conversion function
Thing second = tuple_to_thing(info);
//3 don't use tuple (just use the struct itself if you have to pass it)
Thing three{1,2,true,false};
//4 make member function that uses std::tie
Thing four;
four.tie_from_tuple(info);
}
Provide an explicit constructor and assignment operator:
struct Thing
{
int x;
int y;
bool a;
bool b;
Thing() { }
Thing( int x, int y, bool a, bool b ): x(x), y(y), a(a), b(b) { }
Thing( const std::tuple <int, int, bool, bool> & t )
{
std::tie( x, y, a, b ) = t;
}
Thing& operator = ( const std::tuple <int, int, bool, bool> & t )
{
std::tie( x, y, a, b ) = t;
return *this;
}
};
Hope this helps.
Getting fancy with C++17 template argument deduction and using a proxy object (usage example at bottom):
#include <tuple>
using namespace std;
template <class Tuple>
class FromTuple {
// static constructor, used to unpack argument_pack
template <class Result, class From, size_t... indices>
static constexpr Result construct(index_sequence<indices...>, From&& from_tuple) {
return { get<indices>(forward<
decltype(from_tuple.arguments)>(from_tuple.arguments))... };
}
// used to select static constructor
using Indices = make_index_sequence<
tuple_size_v< remove_reference_t<Tuple> >>;
public:
// construct with actual tuple types only for parameter deduction
explicit constexpr FromTuple(const Tuple& arguments) : arguments(arguments) {}
explicit constexpr FromTuple(Tuple&& arguments) : arguments(move(arguments)) {}
// implicit cast operator delegates to static constructor
template <class Result>
constexpr operator Result() { return construct<Result>(Indices{}, *this); }
private:
Tuple arguments;
};
struct Thing { int x; int y; bool a; bool b; };
int main() {
std::tuple<int, int, bool, bool> info = std::make_tuple(1,2,true,false);
Thing thing0((Thing)FromTuple(info));
Thing thing1{(Thing)FromTuple(info)};
FromTuple from_info(info);
Thing thing2(from_info); // only way to avoid implicit cast operator
Thing thing3{(Thing)from_info};
return 0;
}
This generalizes to any class or struct, not just Thing. Tuple arguments will be passed into the constructor.
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 ) {...}