I have a template function defined only for some types:
template<typename T, std::enable_if_t<std::is_pod_v<T>, bool> = true >
void serialize(const T & t) { /*serialize POD*/ }
template<>
void serialize(const std::string & t) { /*serialize string*/ }
//....
And I would like to add a std::vector specialization that matches only if T can be serialized
template<typename T /*, is_serializable<T> */>
void serialize(const std::vector<T> & t) { /*do something*/ }
How can I make this one matching only if T himself matches a serialize() method?
With C++20 constraints, you might do something like:
template <typename T>
void serialize(const std::vector<T>& v)
requires requires (T inner) {serialize(inner);}
{
// serialize vector
// ...
}
Demo
SFINAE variant can be as simple as:
template<typename T, typename = decltype(serialize(std::declval<T>())) >
void serialize(std::vector<T> const& t)
{ /*do something*/ }
Pre-C++20 solution: Test if you can serialize a type:
template <typename T>
auto is_serializable(T) -> decltype(serialize(std::declval<T>()), std::true_type());
std::false_type is_serializable(...);
template <typename T>
bool constexpr is_serializable_t
= decltype(is_serializable(std::declval<T>()))::value;
With this you can apply the same pattern as you had for the POD types already:
template<typename T, std::enable_if_t<is_serializable_t<T>, bool> = true >
void serialize(std::vector<T> const& t)
{ /*do something*/ }
Demonstration on godbolt.
Edit according to comments:
Actually it does not appear meaningful to serialise a std::vector without serialising all of its elements contained. So the most simple solution (KISS principle...) is: Just doing the serialisation witout adding any tests at all!!!
template<typename T>
void serialize(std::vector<T> const& v)
{
// you'd most likely need:
serialize(v.size());
// but other variants are be possible, too, like e.g. COBS encoding the data,
// then last element to be added, when deserializing again, would be followed
// by the sole 0 (or whatever other sentinel) character encountered...
for(auto& t : v)
{
serialize(t);
}
}
The vector would start to serialise its elements, and these then, if yet other vectors, would just do the same – until reaching the base case where T isn't a vector any more. And now the final decision is made: Does an overload exist handling this final T or not, accordingly the code compiles or not ;)
Demonstration on godbolt.
Related
The goal
I try to create a set of classes that removes boilerplate code for implementing extensions to a game in C++.
For that, I have a designated value class, that can hold one of the following types:
float, std::string, bool, std::vector<value>, void
For that, I would like to have a host class to which I can add one or more method instances like follows:
using namespace std::string_literals;
host h;
h.add(
method<bool, req<std::string>, req<std::string>, opt<bool>>("compare_strings"s,
[](std::string s_orig, std::string s_comp, std::optional<bool> ingore_case) -> bool {
if (ignore_case.has_value() && ignore_case.value()) {
// ... lowercase both
}
return s_orig.compare(s_comp) == 0;
}));
Note that req<T> should be a meta info that a given value is required, opt<T> a meta info that a given value is not required and may only be provided after all required parameters.
The host class now contains a method execute(std::string function, std::vector<value> values) with function and values originating from a method getting char* for method and ´char** argv+ int argcfor values. Theexecutemethod now is supposed to call the correctmethod` instances function
value host::execute(std::string function, std::vector<value> values) {
// get matching method group
std::vector<method> mthds = m_methods[function];
// get matching parameter list
for (method& mthd : mthds) {
if (mthd.can_call(mthds, values)) {
// call generic method
auto res = mthd.call_generic(values);
// pass result back to callee
// return [...]
}
}
// return error back to callee
// return [...]
}
which means that the actual method class now needs to mangle two methods properly can_call and call_generic.
The value class has corresponding template<typename T> bool is() and template<typename T> T get() methods.
What remains
I did have other attempts at this, but as those failed, I deleted them (not very smart in hindside, but needed to get the whole thing out as another person relied on the results working) and now cannot figure out another attempt then prior ... so this is what I am left with as of now:
class method_base
{
public:
template<typename T> struct in { using type = T; };
template<typename T> struct opt { using type = T; };
public:
virtual bool can_call(std::vector<sqf::value> values) = 0;
virtual sqf::value call_generic(std::vector<sqf::value> values) = 0;
};
template<typename T, typename ... TArgs>
class method : public method_base
{
func m_func;
sqf::value val
public:
using func = T(*)(TArgs...);
method(func f) : m_func(f) {}
virtual retval can_call(std::vector<sqf::value> values) override
{
}
};
Appendix
If something is unclear, confusing or you just have further questions, please do ask them. I will try my best to rephrase whatever is unclear as this will help greatly with developing further extensions in the future, possibly defining a "go to" way for how to create extensions in the community for the game in question (Arma 3 just in case somebody wondered)
I may note that this is pretty much my first deep dive into meta programming so things I present may not be possible at all. If so, I kindly would like to ask you if you may also explain why that is so and the thing I attempt is not possible.
The Solution
I do want to express my thanks to all who answered this question again. I ended up combining pretty much parts of all solutions here and pretty much learned a lot on the way. The final implementation I ended up with looks like the following:
namespace meta
{
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<std::optional<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
template <typename ArgType>
struct def_value { static ArgType value() { return {}; } };
template <typename ArgType>
struct get_type { using type = ArgType; };
template <typename ArgType>
struct get_type<std::optional<ArgType>> { using type = ArgType; };
}
struct method {
std::function<bool(const std::vector<value>&)> m_can_call;
std::function<value(const std::vector<value>&)> m_call;
template <typename ... Args, std::size_t... IndexSequence>
static bool can_call_impl(const std::vector<value>& values, std::index_sequence<IndexSequence...> s) {
// values max args
return values.size() <= sizeof...(Args) &&
// for every Arg, either...
(... && (
// the value provides that argument and its the correct type, or...
(IndexSequence < values.size() && sqf::is<sqf::meta::get_type<Args>::type>(values[IndexSequence])) ||
// the value does not provide that argument and the arg is an optional
(IndexSequence >= values.size() && sqf::meta::is_optional_v<Args>)
));
}
template <typename Ret, typename ... Args, std::size_t... IndexSequence>
static value call_impl(std::function<Ret(Args...)> f, const std::vector<value>& values, std::index_sequence<IndexSequence...>) {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(f,
(IndexSequence < values.size() ? sqf::get<sqf::meta::get_type<Args>::type>(values[IndexSequence])
: sqf::meta::def_value<Args>::value())...)
};
}
public:
template <typename Ret, typename ... Args>
method(std::function<Ret(Args...)> f) :
m_can_call([](const std::vector<value>& values) -> bool
{
return can_call_impl<Args...>(values, std::index_sequence_for<Args...>{});
}),
m_call([f](const std::vector<value>& values) -> value
{
return call_impl<Ret, Args...>(f, values, std::index_sequence_for<Args...>{});
})
{
}
bool can_call(const std::vector<value>& values) const { return m_can_call(values); }
value call_generic(const std::vector<value>& values) const { return m_call(values); }
// to handle lambda
template <typename F>
method static create(F f) { return method{ std::function{f} }; }
};
Assumming a way to check current type of value (template <typename T> bool value::isA<T>()) and a way to retrieve the value (template <typename T> /*const*/T& get(/*const*/ value&))
It seems you might do:
struct method
{
template <typename Ret, typename ... Ts>
method(std::function<Ret(Ts...)> f) : method(std::index_sequence<sizeof...(Ts)>(), f)
{}
template <typename Ret, typename ... Ts, std::size_t ... Is>
method(std::index_sequence<Is...>, std::function<Ret(Ts...)> f) :
isOk([](const std::vector<value>& values) {
return ((values.size() == sizeof...(Is)) && ... && values[Is].isA<Ts>());
}),
call([f](const std::vector<value>& values){
return f(get<Ts>(values[Is])...);
})
{}
// to handle lambda
template <typename F>
static fromCallable(F f) { return method{std::function{f}}; }
std::function<bool(const std::vector<value>&)> isOk;
std::function<value(const std::vector<value>&)> call;
};
Here's a quick example including the machinery for ret<T> and opt<T>. You haven't given any information on what value is, so I'm going to assume something like:
struct value {
// using `std::monostate` instead of `void`
std::variant<float, std::string, bool, std::vector<value>, std::monostate> data;
};
(I'm assuming c++17 for this answer.)
From there, we need our metatypes and a few traits to branch off them. I implement them using partial specialisations, but there are other ways too.
// types to determine optional vs. required
template <typename T>
struct req { using type = T; };
template <typename T>
struct opt { using type = T; };
// trait to determine if it's an optional type
template <typename ArgType>
struct is_optional : std::false_type {};
template <typename T>
struct is_optional<opt<T>> : std::true_type {};
template <typename ArgType>
inline constexpr bool is_optional_v = is_optional<ArgType>::value;
// get the "real" function parameter type
template <typename ArgType>
struct real_type;
template <typename ArgType>
using real_type_t = typename real_type<ArgType>::type;
template <typename T>
struct real_type<req<T>> { using type = T; };
template <typename T>
struct real_type<opt<T>> { using type = std::optional<T>; };
Now we implement method. I'll use a similar polymorphic relationship with method_base as you do in your partial demo; I also template on the function type passed in, to allow e.g. the functions to use const references to the type instead of the type itself.
The implementation itself uses the common trick of delegating to helper functions with std::index_sequence and fold expressions to "iterate" through the variadic template args.
// base class for polymorphism
struct method_base {
virtual ~method_base() = default;
virtual bool can_call(const std::vector<value>& values) const = 0;
virtual value call_generic(const std::vector<value>& values) const = 0;
};
// provide a different method implementation for each set of args
// I also overload on
template<typename RetType, typename Fn, typename... Args>
struct method : method_base {
private:
Fn func;
static_assert(std::is_invocable_r_v<RetType, Fn, real_type_t<Args>...>,
"function must be callable with given args");
public:
// accept any function that looks sort of like what we expect;
// static assert above makes sure it's sensible
template <typename G>
method(G&& func) : func(std::forward<G>(func)) {}
template <std::size_t... Is>
bool can_call_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
// for every Arg, either...
return (... and (
// the value provides that argument and its the correct type, or...
(Is < values.size() and std::holds_alternative<typename Args::type>(values[Is].data))
// the value does not provide that argument and the arg is an optional
or (Is >= values.size() and is_optional_v<Args>)
));
}
bool can_call(const std::vector<value>& values) const override {
return can_call_impl(values, std::index_sequence_for<Args...>{});
}
template <std::size_t... Is>
value call_generic_impl(const std::vector<value>& values, std::index_sequence<Is...>) const {
return {
// call the function with every type in the value set,
// padding with empty std::optionals otherwise
std::invoke(func,
(Is < values.size() ? std::get<typename Args::type>(values[Is].data)
: real_type_t<Args>{})...)
};
}
value call_generic(const std::vector<value>& values) const override {
return call_generic_impl(values, std::index_sequence_for<Args...>{});
}
};
I'll also create a helper function to make methods:
template <typename RetType, typename... Args, typename Fn>
std::unique_ptr<method_base> make_method(Fn&& func) {
return std::make_unique<method<RetType, std::decay_t<Fn>, Args...>>(std::forward<Fn>(func));
}
Live example.
It's not perfect, but this should give you a general idea of how to do it.
Change your method to:
method< R(Args...) >
your tags seem useless. Detect optional with ... std::optional.
For storage, use std variant. Use some non-void type for void (I don't care what).
As a first pass we aim for perfect compatibility.
template<class...Args>
struct check_signature {
bool operator()( std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
std::size_t i=0;
return (std::holds_alternative<Args>(values[i++])&&...);
}
};
this can be stored in a std::function<bool(std::span<value const>)> or just called in your class impementation.
Similar code can store the callable.
template<class F, class R, class...Args>
struct execute {
F f;
template<std::size_t...Is>
R operator()( std::index_sequence<Is...>, std::span<value const> values ) const {
if (sizeof...(Args) != values.size()) return false;
return f( std::get<Args>(values[Is])... );
}
R operator()( std::span<value const> values ) const {
return (*this)( std::make_index_sequence<sizeof...(Args)>{}, values );
}
};
some work may have to be done for the fake void.
Your method is now a aggregate.
struct method {
std::function<bool(std::span<value const>)> can_call;
std::function<value(std::span<value const>)> execute;
};
if you want it to be. The two template objects above can be stored in these two std functions.
There are probably tpyos, I just wrote this on my phone and have not tested it.
Extending this to cover optional args is a little bit of work. But nothing hard.
In both cases, you'll write a helper function that says if the argument is compatible or generates the value based on if you are past the end of the incoming vector.
Ie, std::get<Args>(values[Is])... becomes getArgFrom<Is, Args>{}(values)..., and we specialize for std optional producing nullopt if Is>values.size().
To make more sence for the question title consider the following code:
template <typename T>
void write(const T& /*data*/)
{
// use data as const
}
template <typename T>
void read(T& /*data*/)
{
// modify data
}
struct S {
int a;
int b;
// a lot of primitive types here
};
void output(const S& s)
{
write(s.a);
write(s.b);
// ...
}
void input(S& s)
{
read(s.a);
read(s.b);
// ...
}
int main()
{
S s;
input(s);
output(s);
}
I have write and read functions that manipulate primitive data like int, double etc. I have several struct types like S that contains a lot of primitive data members, and I need to input and output such types via corresonding functions input and output (there are many of overloads for distinct types). As you can see the content of output and input functions mostly the same, only the inner functions and type constness are different. I would like to make some generic (template) function use to eliminate code duplicates from input/output functions.
I think the template signature will be the following (but I can miss something):
template <typename F, typename T>
void use(T& t) { ... }
So I could call the use function from input/output like this:
use<write, S>(s); // in output function
use<read, S>(s); // in input fuction
How to archive this (or similar) behaviour to eliminate code duplications? I'm using C++14.
template <typename S, typename F>
void use(S&& s, F&& f)
{
f(s.a);
f(s.b);
f(s.c);
}
Usage:
use(s, [](const auto& x){ write(x); });
use(s, [](auto& x) { read(x); });
live example on wandbox.org
If you need multiple types:
template <typename S, typename Target>
using EnableFor = std::enable_if_t<std::is_same_v<std::decay_t<S>, Target>>;
template <typename S, typename F>
auto use(S&& s, F&& f) -> EnableFor<S, S0>
{
f(s.a);
f(s.b);
f(s.c);
}
template <typename S, typename F>
auto use(S&& s, F&& f) -> EnableFor<S, S1>
{
f(s.d);
f(s.e);
f(s.f);
}
Usage:
int main()
{
S0 s0;
use(s0, [](const auto& x){ write(x); });
use(s0, [](auto& x) { read(x); });
S1 s1;
use(s1, [](const auto& x){ write(x); });
use(s1, [](auto& x) { read(x); });
}
live example on wandbox.org
You can define an interface of the template being used:
template<class M, class S>
void use(S &&s) {
M::method(s.a);
M::method(s.b);
};
Then implement it:
template <typename T>
void write(const T& /*data*/)
{
// use data as const
}
struct writer {
template <typename T>
static void method(const T& t)
{
write(t);
}
};
Now you can use it:
use<writer>(s);
For read, it's the same:
struct reader {
template <typename T>
static void method(T& t)
{
read(t);
}
};
use<reader>(s);
I believe I understood what you want, but it is impossible. Currently there is no way to implement in C++ generic streaming for all classes. One would need access to information like number of fields the class has, their relative address as well as type in compile time.
Currently they are working on adding this to C++ for many years, seek Reflection TS. As far as I know it won't be ready for C++20.
However, you can organize your code so that each read/write method is easy to implement. For example:
class A : public B, public C
{
private:
vector<int> m_v;
int m_x;
};
Then implement write for it as
template<>
void write(const A& a)
{
write((const B&)a);
write((const C&)a);
write(a.m_v);
write(a.m_x);
}
While for common data structures like std::vector<T> implement write via write<T>. It takes time but making it technical and using redirections can save a lot of code and time.
(You need to make write a friend function, or a member function to let it access private members)
Extremely new to c++ however have a question regarding templates
Suppose I have a simple template class as defined below:
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
c.push_back(value);
}
};
The aim of the class being to accept any type of collection, and allow a user to insert the correct type of value for the specified typename Collection.
The obvious problem is that this is only going to work for types which have a push_back method defined, which means it would work with list however not with set.
I started reading about template specialization to see if that'd be any help, however I don't think this would provide a solution as the type contained within the set would have to be known.
How would this problem be approached in c++?
You can use std::experimental::is_detected and if constexpr to make it work:
template<class C, class V>
using has_push_back_impl = decltype(std::declval<C>().push_back(std::declval<V>()));
template<class C, class V>
constexpr bool has_push_back = std::experimental::is_detected_v<has_push_back_impl, C, V>;
template<typename Collection>
class MySack {
private:
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
if constexpr (has_push_back<Collection, value_type>) {
std::cout << "push_back.\n";
c.push_back(value);
} else {
std::cout << "insert.\n";
c.insert(value);
}
}
};
int main() {
MySack<std::set<int>> f;
f.add(23);
MySack<std::vector<int>> g;
g.add(23);
}
You can switch to insert member function, which has the same syntax for std::vector, std::set, std::list, and other containers:
void add(const value_type& value) {
c.insert(c.end(), value);
}
In C++11, you might also want to create a version for rvalue arguments:
void add(value_type&& value) {
c.insert(c.end(), std::move(value));
}
And, kind-of simulate emplace semantics (not truly in fact):
template <typename... Ts>
void emplace(Ts&&... vs) {
c.insert(c.end(), value_type(std::forward<Ts>(vs)...));
}
...
int main() {
using value_type = std::pair<int, std::string>;
MySack<std::vector<value_type>> v;
v.emplace(1, "first");
MySack<std::set<value_type>> s;
s.emplace(2, "second");
MySack<std::list<value_type>> l;
l.emplace(3, "third");
}
I started reading about template specialization to see if that'd be
any help, however I don't think this would provide a solution as the
type contained within the set would have to be known.
You can partially specialize MySack to work with std::set.
template <class T>
class MySack<std::set<T>> {
//...
};
However, this has the disadvantage that the partial specialization replaces the whole class definition, so you need to define all member variables and functions again.
A more flexible approach is to use policy-based design. Here, you add a template parameter that wraps the container-specific operations. You can provide a default for the most common cases, but users can provide their own policy for other cases.
template <class C, class V = typename C::value_type>
struct ContainerPolicy
{
static void push(C& container, const V& value) {
c.push_back(value);
}
static void pop(C& container) {
c.pop_back();
}
};
template <class C, class P = ContainerPolicy<C>>
class MySack
{
Collection c;
public:
typedef typename Collection::value_type value_type;
void add(const value_type& value) {
P::push(c, value);
}
};
In this case, it is easier to provide a partial template specialization for the default policy, because it contains only the functionality related to the specific container that is used. Other logic can still be captured in the MySack class template without the need for duplicating code.
Now, you can use MySack also with your own or third party containers that do not adhere to the STL style. You simply provide your own policy.
struct MyContainer {
void Add(int value);
//...
};
struct MyPolicy {
static void push(MyContainer& c, int value) {
c.Add(value);
}
};
MySack<MyContainer, MyPolicy> sack;
If you can use at least C++11, I suggest the creation of a template recursive struct
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
to manage precedence in case a container can support more than one adding functions.
So you can add, in the private section of your class, the following template helper functions
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
Observe that the decltype() part enable they (through SFINAE) only if the corresponding method (push_back(), insert() or push_front()) is enabled.
Now you can write add(), in the public section, as follows
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
The tag<2> element make so the tag<2> addHelper() method is called, if available (if push_back() is available for type C), otherwise is called the tag<1> method (the insert() one) if available, otherwise the tag<0> method (the push_front() one) is available. Otherwise error.
Also observe the T && t and std::forward<T>(t) part. This way you should select the correct semantic: copy or move.
The following is a full working example
#include <map>
#include <set>
#include <list>
#include <deque>
#include <vector>
#include <iostream>
#include <forward_list>
#include <unordered_map>
#include <unordered_set>
template <std::size_t N>
struct tag : public tag<N-1U>
{ };
template <>
struct tag<0U>
{ };
template <typename C>
class MySack
{
private:
C c;
template <typename D, typename T>
auto addHelper (T && t, tag<2> const &)
-> decltype((void)std::declval<D>().push_back(std::forward<T>(t)))
{ c.push_back(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<1> const &)
-> decltype((void)std::declval<D>().insert(std::forward<T>(t)))
{ c.insert(std::forward<T>(t)); }
template <typename D, typename T>
auto addHelper (T && t, tag<0> const &)
-> decltype((void)std::declval<D>().push_front(std::forward<T>(t)))
{ c.push_front(std::forward<T>(t)); }
public:
template <typename T>
void add (T && t)
{ addHelper<C>(std::forward<T>(t), tag<2>{}); }
};
int main ()
{
MySack<std::vector<int>> ms0;
MySack<std::deque<int>> ms1;
MySack<std::set<int>> ms2;
MySack<std::multiset<int>> ms3;
MySack<std::unordered_set<int>> ms4;
MySack<std::unordered_multiset<int>> ms5;
MySack<std::list<int>> ms6;
MySack<std::forward_list<int>> ms7;
MySack<std::map<int, long>> ms8;
MySack<std::multimap<int, long>> ms9;
MySack<std::unordered_map<int, long>> msA;
MySack<std::unordered_multimap<int, long>> msB;
ms0.add(0);
ms1.add(0);
ms2.add(0);
ms3.add(0);
ms4.add(0);
ms5.add(0);
ms6.add(0);
ms7.add(0);
ms8.add(std::make_pair(0, 0L));
ms9.add(std::make_pair(0, 0L));
msA.add(std::make_pair(0, 0L));
msB.add(std::make_pair(0, 0L));
}
I require a simple way to obtain the count / length / size of an object of class T where T is some sort of collection type, such as a std::map, std::list, std::vector, CStringArray, CString, std::string, …
For most of the standard types, T::size() is the correct answer, for most of the MFC classes T::GetSize() is correct and for CString, it is T::GetLength().
I want to have a like:
template <typename T> auto size(const T & t)
...which evaluates to the correct member function call.
It seems like there should be a simple way to invoke a traits template on T which has a size(const T & t) member, which itself uses SFINAE to exist or not exist, and if it exists, then it is by definition calling an appropriate t.size_function() to return the count of elements in that instance of a T.
I could write an elaborate has_member type-trait template - there are a few examples on stackoverflow - all of them quite convoluted for what seems to me "there must be a simpler approach". With C++ 17, it seems like this issue should be easily and elegantly solved?
These discussions here and here seems to use an inelegant solution with some of the answers using preprocessor macros to get the job done. Is that still necessary?
But... surely, there must be a way to use the fact that calling the correct member function on a T is compilable, and calling the wrong one fails to compile - can't that be used directly to create the correct type traits wrapper for a given type T?
I would like something along the lines of:
template <typename T>
auto size(const T & collection)
{
return collection_traits<T>::count(collection);
}
Where the exact specialization of collection_traits<T> is selected because it is the only one that fits for T (i.e. it calls the correct instance method).
You can use expression SFINAE and multiple overloads.
The idea is as follows: check if x.size() is a valid expression for your type - if it is, invoke and return it. Repeat for .getSize and .getLength.
Given:
struct A { int size() const { return 42; } };
struct B { int getSize() const { return 42; } };
struct C { int GetLength() const { return 42; } };
You can provide:
template <typename T>
auto size(const T& x) -> decltype(x.size()) { return x.size(); }
template <typename T>
auto size(const T& x) -> decltype(x.getSize()) { return x.getSize(); }
template <typename T>
auto size(const T& x) -> decltype(x.GetLength()) { return x.GetLength(); }
Usage:
int main()
{
size(A{});
size(B{});
size(C{});
}
live example on wandbox.org
This solution is easy to extend and seamlessly works with containers that are templatized.
What if a type exposes two getters?
The solution above would result in ambiguity, but it's easy to fix by introducing a ranking/ordering that solves that.
Firstly, we can create a rank class that allows us to arbitrarily prioritize overloads:
template <int N> struct rank : rank<N - 1> { };
template <> struct rank<0> { };
rank<N> is implicitly convertible to rank<N - 1>. An exact match is better than a chain of conversions during overload resolution.
Then we can create a hierarchy of size_impl overloads:
template <typename T>
auto size_impl(const T& x, rank<2>)
-> decltype(x.size()) { return x.size(); }
template <typename T>
auto size_impl(const T& x, rank<1>)
-> decltype(x.getSize()) { return x.getSize(); }
template <typename T>
auto size_impl(const T& x, rank<0>)
-> decltype(x.GetLength()) { return x.GetLength(); }
Finally we provide an interface function that begins the dispatch to the right size_impl overload:
template <typename T>
auto size(const T& x) -> decltype(size_impl(x, rank<2>{}))
{
return size_impl(x, rank<2>{});
}
Using a type like D below
struct D
{
int size() const { return 42; }
int getSize() const { return 42; }
int GetLength() const { return 42; }
};
will now choose the rank<2> overload of size_impl:
live example on wandbox
The simplest solution, IMO, is function overloading.
// Default implementation for std containers.
template <typename Container>
std::size_t size(Container const& c) { return c.size(); }
// Overloads for others.
std::size_t size(CStringArray const& c) { return c.GetSize(); }
std::size_t size(CString const& c) { return c.GetLength(); }
// ... etc.
You need expression SFINAE, and you must play nice with other types which might decide to conform to both interfaces, so study std::size().
The goal is to augment std::size() to work on all types which follow at least one of the conventions, as long as they don't mess up trying to follow any of them.
#include <type_traits>
#include <iterator>
namespace internal {
// Avoid conflict with std::size()
template <class C>
auto size_impl(const C& c, int) -> decltype((void)c.size());
// Avoid conflict with std::size()
template <class T, std::size_t N>
void size_impl(const T (&array)[N], int);
template <class C>
constexpr auto size_impl(const C& c, long)
noexcept(noexcept(c.GetLength()))
-> decltype(c.GetLength())
{ return c.GetLength(); }
template <class C>
constexpr auto size_impl(const C& c, long long)
noexcept(noexcept(c.getSize()))
-> decltype(c.getSize())
{ return c.getSize(); }
};
template <class T>
using enable_if_not_void_t = std::enable_if_t<!std::is_void<T>(), T>;
using std::size;
template <class C>
constexpr auto size(const C& c)
noexcept(noexcept(internal::size_impl(c, 0)))
-> enable_if_not_void_t<decltype(internal::size_impl(c, 0))>
{ return internal::size_impl(c, 0); }
You can get arbitrary levels of precedence for extending things using templates and inheritance:
template <std::size_t N>
struct priority : priority<N - 1> {};
template <>
struct priority<0> {};
Something like the proposed Abbreviated Lambdas for Fun and Profit would greatly simplify things.
Given the following:
StreamLogger& operator<<(const char* s) {
elements.push_back(String(s));
return *this;
}
StreamLogger& operator<<(int val) {
elements.push_back(String(asString<int>(val)));
return *this;
}
StreamLogger& operator<<(unsigned val) {
elements.push_back(String(asString<unsigned>(val)));
return *this;
}
StreamLogger& operator<<(size_t val) {
elements.push_back(String(asString<size_t>(val)));
return *this;
}
is there a way to eliminate repetition? I want to use templates, but I only want it for the following types: const char* int, unsigned, and size_t
Really, in "vanilla" C++ you either write the by hand, for specific types, or you use a template like dirkgently suggested.
That said, if you can use Boost this does what you want:
template <class T>
StreamLogger& operator<<(T val)
{
typedef boost::mpl::vector<const char*, int,
unsigned, size_t> allowed_types;
BOOST_MPL_ASSERT_MSG(boost::mpl::contains<allowed_types, T>::value,
TYPE_NOT_ALLOWED, allowed_types);
// generic implementation follows
elements.push_back(boost::lexical_cast<std::string>(val));
return *this;
}
This will generate a compile-time error with the message TYPE_NOT_ALLOWED embedded in it if the type being compiled is not contained in the typelist.
Also, since this answer requires Boost I just used lexical_cast. You'll note you're repeating that code, and that's bad. Consider wrapping that functionality into a function.
If you aren't able to use Boost, you can simulate this pretty easily with some type traits:
template <typename T, typename U>
struct is_same
{
static const bool value = false;
};
template <typename T>
struct is_same<T, T>
{
static const bool value = true;
};
template <bool>
struct static_assert;
template <>
struct static_assert<true> {}; // only true is defined
// not the best, but it works
#define STATIC_ASSERT(x) static_assert< (x) > _static_assert_
template <class T>
StreamLogger& operator<<(T val)
{
STATIC_ASSERT(is_same<const char*, T>::value ||
is_same<int, T>::value ||
is_same<unsigned, T>::value ||
is_same<size_t, T>::value);
// generic implementation follows
elements.push_back(boost::lexical_cast<std::string>(val));
return *this;
}
This will also generate a compile-time error if the assert fails, though the code isn't as sexy. :( <- Not sexy
Something like this should work:
template <class T>
StreamLogger& operator<<(T val) {
istringstream s;
s << val;
elements.push_back(s.str()); // assuming elements is a vector<string>
return *this;
}