I work on a code base where the C++ API is about to change (master branch and feature branch) and I want to protect my code against the change, such that my code builds against both versions. As minimum working example godbolt/compiler-explorer link:
common:
class SmallWidget {
private:
int m_i;
public:
int i() { return m_i; }
};
master:
class BigWidget {
private:
std::vector<SmallWidget*> m_v;
public:
auto& widgets() { return m_v; }
};
feature branch:
class BigWidget {
private:
std::vector<SmallWidget> m_v;
public:
auto& widgets() { return m_v; }
};
/**/
For the master version I have some call code:
int single_op(BigWidget& w) {
return (*w.widgets().begin())->i(); // asterisk will need to go
}
std::vector<int> do_stuff(std::vector<BigWidget> inputs) {
std::vector<int> retval;
for (auto w : inputs) {
retval.push_back(single_op(w));
}
return retval;
}
The API does not come with a preprocessor variable to select with
#if WIDGET_VERSION == OLD
return (*w.widgets().begin())->i(); // asterisk will need to go
#else
return (w.widgets().begin())->i(); // asterisk gone
#endif
I was trying to detect the return type of widgets with sfinae:
// version for the master branch API
template <typename = typename std::enable_if<std::is_same<
std::remove_const<std::remove_reference<decltype(**(
std::declval<BigWidget>().widgets().begin()))>::type>::type,
SmallWidget>::value>::type>
int single_op(BigWidget& w) {
return (*w.widgets().begin())->i();
}
// version for the feature branch API
template <typename = typename std::enable_if<std::is_same<
std::remove_const<std::remove_reference<decltype(*(
std::declval<BigWidget>().widgets().begin()))>::type>::type,
SmallWidget>::value>::type>
int single_op(BigWidget& w) { return w.widgets().begin()->i(); }
But the compilers are not happy about that. clang says:
<source>:43:46: error: failed requirement 'std::is_same<std::remove_const<std::remove_reference<decltype(* (std::declval<BigWidget>().widgets().begin()))>::type>::type, SmallWidget>::value'; 'enable_if' cannot be used to disable this declaration
template
Is there a way to use sfinae to enable the right code here?
I also tried using std::enable_if for the return code or function arguments, but my understanding is that the compiler always sees the signature int single_op(BigWidget&) for both versions.
Create a small helper function that will take either type and return the type you want.
SmallWidget &getWidget(SmallWidget *w) { return *w; }
SmallWidget &getWidget(SmallWidget &w) { return w; }
int single_op(BigWidget &w) {
return getWidget(*w.widgets().begin()).i();
}
You can also provide overloads for const versions.
I could come up with this version:
// version for the feature branch API
template <typename bigtype>
int single_op(
typename std::enable_if<
std::is_same<
std::remove_const<std::remove_reference<decltype(
*(std::declval<BigWidget>().widgets().begin()))>::type>::type,
SmallWidget>::value,
bigtype>::type& w) {
return w.widgets().begin()->i();
}
I added one additional template argument for the BigWidget type (despite being known) and use it for the function argument of single_op. Now the compiler can go through both template definitions, arrive with one possible solution for the overload set and disables the unused code.
As drawback, I now have to spell out the template argument on the call side
retval.push_back(single_op<decltype(w)>(w));
But I suspect there are better solutions out there.
Related
My problem is pretty simple, i want to use lambda's in the same way i may use a functor as a 'comparator', let me explain a little better. I have two big structs, both of them have their own implementation of operator<, and i have also a useless class (this is just the name of the class in the context of this question) which use the two struct, everything looks like this:
struct be_less
{
//A lot of stuff
int val;
be_less(int p_v):val(p_v){}
bool operator<(const be_less& p_other) const
{
return val < p_other.val;
}
};
struct be_more
{
//A lot of stuff
int val;
be_more(int p_v):val(p_v){}
bool operator<(const be_more& p_other) const
{
return val > p_other.val;
}
};
class useless
{
priority_queue<be_less> less_q;
priority_queue<be_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(elem);
more_q.emplace(elem);
}
}
};
I whould like to remove the duplication in the two struct's, the simpliest idea is to make the struct a template and provide two functor to do the comparison job:
template<typename Comp>
struct be_all
{
//Lot of stuff, better do not duplicate
int val;
be_all(int p_v):val{p_v}{}
bool operator<(const be_all<Comp>& p_other) const
{
return Comp()(val,p_other.val);
}
};
class comp_less
{
public:
bool operator()(int p_first,
int p_second)
{
return p_first < p_second;
}
};
class comp_more
{
public:
bool operator()(int p_first,
int p_second)
{
return p_first > p_second;
}
};
typedef be_all<comp_less> all_less;
typedef be_all<comp_more> all_more;
class useless
{
priority_queue<all_less> less_q;
priority_queue<all_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(elem);
more_q.emplace(elem);
}
}
};
This work pretty well, now for sure i dont have any duplication in the struct code at the price of two additional function object. Please note that i'm very simplifying the implementation of operator<, the hipotetic real code does much more than just comparing two ints.
Then i was thinking about how to do the same thing using lambda (Just as an experiment).The only working solution i was able to implement is:
template<typename Comp>
struct be_all
{
int val;
function<bool(int,int)> Comparator;
be_all(Comp p_comp,int p_v):
Comparator(move(p_comp)),
val{p_v}
{}
bool operator<(const be_all& p_other) const
{
return Comparator(val, p_other.val);
}
};
auto be_less = [](int p_first,
int p_second)
{
return p_first < p_second;
};
auto be_more = [](int p_first,
int p_second)
{
return p_first > p_second;
};
typedef be_all<decltype(be_less)> all_less;
typedef be_all<decltype(be_more)> all_more;
class useless
{
priority_queue<all_less> less_q;
priority_queue<all_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(be_less,elem);
more_q.emplace(be_more,elem);
}
}
};
This implementation not only add a new member to the data containing struct, but have also a very poor performance, i prepared a small test in which i create one instance for all the useless class i've show you here, every time i feed the constructor with a vector full of 2 milion integers, the results are the following:
Takes 48ms to execute the constructor of the first useless class
Takes 228ms to create the second useless class (functor)
Takes 557ms to create the third useless class (lambdas)
Clearly the price i pay for the removed duplication is very high, and in the original code the duplication is still there. Please note how bad is the performance of the third implementation, ten times slower that the original one, i believed that the reason of the third implementation being slower than the second was because of the additional parameter in the constructor of be_all... but:
Actually there's also a fourth case, where i still used the lambda but i get rid of the Comparator member and of the additional parameter in be_all, the code is the following:
template<typename Comp>
struct be_all
{
int val;
be_all(int p_v):val{p_v}
{}
bool operator<(const be_all& p_other) const
{
return Comp(val, p_other.val);
}
};
bool be_less = [](int p_first,
int p_second)
{
return p_first < p_second;
};
bool be_more = [](int p_first,
int p_second)
{
return p_first > p_second;
};
typedef be_all<decltype(be_less)> all_less;
typedef be_all<decltype(be_more)> all_more;
class useless
{
priority_queue<all_less> less_q;
priority_queue<all_more> more_q;
public:
useless(const vector<int>& p_data)
{
for(auto elem:p_data)
{
less_q.emplace(elem);
more_q.emplace(elem);
}
}
};
If i remove auto from the lambda and use bool instead the code build even if i use Comp(val, p_other.val) in operator<.
What's very strange to me is that this fourth implementation (lambda without the Comparator member) is even slower than the other, at the end the average performance i was able to register are the following:
48ms
228ms
557ms
698ms
Why the functor are so much faster than lambdas in this scenario? I was expecting lambda's to be at least performing good as the ordinary functor, can someone of you comment please? And is there any technial reason why the fourth implementation is slower than the third?
PS:
The compilator i'm using is g++4.8.2 with -O3. In my test i create for each useless class an instance and using chrono i take account of the required time:
namespace benchmark
{
template<typename T>
long run()
{
auto start=chrono::high_resolution_clock::now();
T t(data::plenty_of_data);
auto stop=chrono::high_resolution_clock::now();
return chrono::duration_cast<chrono::milliseconds>(stop-start).count();
}
}
and:
cout<<"Bad code: "<<benchmark::run<bad_code::useless>()<<"ms\n";
cout<<"Bad code2: "<<benchmark::run<bad_code2::useless>()<<"ms\n";
cout<<"Bad code3: "<<benchmark::run<bad_code3::useless>()<<"ms\n";
cout<<"Bad code4: "<<benchmark::run<bad_code4::useless>()<<"ms\n";
The set of input integers is the same for all, plenty_of_data is a vector full of 2 million intergers.
Thanks for your time
You are not comparing the runtime of a lambda and a functor. Instead, the numbers indicate the difference in using a functor and an std::function. And std::function<R(Args...)>, for example, can store any Callable satisfying the signature R(Args...). It does this through type-erasure. So, the difference you see comes from the overhead of a virtual call in std::function::operator().
For example, the libc++ implementation(3.5) has a base class template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes> __base with a virtual operator(). std::function stores a __base<...>*. Whenever you create an std::function with a callable F, an object of type template<class F, class _Alloc, class R, class ...Args> class __func is created, which inherits from __base<...> and overrides the virtual operator().
Suppose I have a class:
class Widget {
public:
void initialize() {
// hurr-durr
};
int computeAnswer() {
return -42;
};
std::string getQuestion() {
return "The question";
};
};
It performs some computation, can do whatever it wants.
Now I want to augment it - apply an aspect, say one that logs each method call.
If I implemented this by hand, I'd implement all methods in this fashion:
int LoggingWidget::computeAnswer(){
log << 'Calling method computeAnswer';
int result = Widget::computerAnswer();
log << 'Result = ' << result;
return result;
}
I'd like the solution to be as generic as possible (I don't want to manually forward all calls), so the possible usages could include one of these (whichever is possible)
Widget* w = new LoggingWidget(); // either a class that inherits from Widget
// and automatically forwards all calls.
Widget* w = new Logging<Widget>(); // or a template that does this.
so that when I call
int result = w.computeAnswer();
The calls will be logged. Perhaps the new ellipsis operator (...) could come in handy here?
This isn't directly possible, since you can't inspect a class to see which members it has.
However, you can do something close:
Logging<Widget> w(widget);
w([&](Widget& w){
return w.computeAnswer();
});
Where Logging::operator() looks like follows:
/* somewhere in class: T wrapped; */
template<class F>
auto operator()(F&& f)
-> decltype(f(wrapped))
{
pre_log();
auto&& result = f(wrapped);
post_log(result);
return result;
}
It won't get better much better than this for totally generic code, since C++ has no (static) reflection.
Expanding on Xeo's answer, if you use decltype or result_of rather than auto && you also get copy elision.
template<typename F>
auto operator()(F &&f) -> decltype(std::forward<F>(f)(wrapped))
{
pre_log();
decltype(std::forward<F>(f)(wrapped)) result = std::forward<F>(f)(wrapped);
post_log(result);
return result;
}
In C++14 you can shorten this to:
template<typename F>
decltype(auto) operator()(F &&f)
{
pre_log();
decltype(auto) result = std::forward<F>(f)(wrapped);
post_log(result);
return result;
}
This is more of a question of how the C++ compiler handles const typeid calls.
Hello! I am trying to make a tuple-style class, configured in such a way that I don't have to rewrite a bunch of the code with specializations.
So this is the general idea:
struct null_type{};
template <typename T1,typename T2=null_type,typename T3=null_type>
class ptestclass
{
private:
template<typename K1,typename K2,typename K3>
class barclass
{
public:
static inline void bar(std::tuple<K1,K2,K3>& vals,K1* otherval1,K2* otherval2,K3* otherval3)
{
Foo(tr1::get<0>(vals),*otherval1);
Foo(tr1::get<1>(vals),*otherval2);
Foo(tr1::get<2>(vals),*otherval3);
}
};
template<typename K1,typename K2>
class barclass<K1,K2,null_type>
{
public:
static inline void bar(std::tuple<K1,K2,null_type>& vals,K1* otherval1,K2* otherval2,null_type* otherval3)
{
Foo(tr1::get<0>(vals),*otherval1);
Foo(tr1::get<1>(vals),*otherval2);
}
};
template<typename K1>
class barclass<K1,null_type,null_type>
{
public:
static inline void bar(std::tuple<K1,null_type,null_type>& vals,K1* otherval1,null_type* otherval2,null_type* otherval3)
{
Foo(tr1::get<0>(vals),*otherval1);
}
};
/*
*Old Bar function...much more readable than bar class, but you cannot partially specialize
*member functions of a class
*
void inline bar(std::tuple<T1,T2,T3> otherval)
{
if (typeid(T1) != typeid(null_type))//constant check hopfully optomized out
{
Foo(vals.get(1),otherval.get(1));
}
if (typeid(T2) != typeid(null_type))//constant check hopfully optomized out
{
Foo(vals.get(2),otherval.get(2));
}
if(typeid(T3) != typeid(null_type))//constant check hopfully optomized out
{
Foo(vals.get(3),otherval.get(3));
}
}
*/
std::tuple<T1,T2,T3> vals;
template<typename K>
void static inline Foo(K& val,K& otherval)
{
//inlineable, short function that is called many (millions) of times per iteration
val += otherval;
}
template<>
void inline Foo<null_type>(null_type& val,null_type& otherval)
{
//inlineable, short function that is called many (millions) of times per iteration
throw "Foo called on null type";
}
public:
ptestclass()
{
printf("made object");
}
void one_iteration(T1* otherval1,T2* otherval2,T3* otherval3,size_t count)
{
for (int i = 0; i < count; ++i)
{
barclass<T1,T2,T3>::bar(vals,otherval1+i,otherval2+i,otherval3+i);
}
}
};
//exposed public class with specialized one_iteration interfaces
template <typename T1,typename T2=null_type,typename T3=null_type>
class testclass : public ptestclass<T1,T2,T3>
{
public:
void one_iteration(T1* otherval1,T1* otherval2,T1* otherval3,size_t count)
{
ptestclass::one_iteration(otherval1,otherval2,otherval3,count);
}
};
template <typename T1>
class testclass<T1,null_type,null_type> : public ptestclass<T1,null_type,null_type>
{
public:
void one_iteration(T1* otherval1,size_t count)
{
ptestclass::one_iteration(otherval1,NULL,NULL,count);
}
};
So my question is is this optimization even possible within C++? If not, it will probably make more sense for me to use an inheritance model on the child nodes rather then a template at this level. However, I am trying to avoid the continual check of the number of types specified and the cost of indirection.
I'm going to start diving into the assembly to see if that is what the compiler does...Just in case this is not standardized behavior, I'm using the Microsoft Visual C++ Compiler 10.0.
I think I misunderstood your question when I put my earlier comment.
Assuming you can use c++11, or you can use boost, you could use something like !std::is_same< T1, null_type >::value /*or boost::is_same...*/ instead of typeid(T1) != typeid(null_type). This uses TMP to resolve to a compile-time constant, which most compilers would have no trouble optimizing away.
This is more of a question of how the C++ compiler handles const typeid calls.
I didn't answer this specific question, but if I understand what you were actually looking for, the above should suffice.
I need to instantiate a free template function (FTF) within a template class (TC). The FTF takes as a template parameter one of the template parameters of the TC. The TC also holds generic pointers to these FTF's, and these functions are called through the pointers.
The step of taking a pointer to a FTF is not enough to instantiate it, and I receive linker errors from the GCC toolchain. MSDN illustrates FTF specification as so -- however my instantion of the FTF is dependant on a template parameter of my TC, and therefore the FTF instantiation cannot be placed in free scope.
Is this possible ? I am attaching some basic generated code, the issue is in the constructor of the class test_service, where I assign the pointer of a free function into a custom container. I get a linker error telling me the free function cannot be found (uninstantiated). I know that specifying a call to the template function in the class somewhere will produce a instantiation, however I am only going to be making a call via a pointer.
#include "rpc_common.h"
#include <boost/cstdint.hpp>
namespace rubble { namespace rpc {
struct test_service_dummy_tag{};
template<typename T>
class test_service_skel
{
public:
bool Init() {}
bool TearDown() {}
bool test_one(TestRequest,TestResponse){};
private:
};
template<typename T_IMPL>
bool test_service_test_one(T_IMPL & impl,ClientRequest & request)
{
return 0;
}
template<typename T_IMPL=test_service_skel<test_service_dummy_tag> >
class test_service
{
public:
test_service()
{
// uncomment the following two lines and a instantiation will occur.
// ClientRequest cr;
//test_service_test_one<T_IMPL>(m_impl,cr);
m_dispatch_table.SetEntry( Oid("test_one",0),(void *) & test_service_test_one<T_IMPL>);
}
bool Init() { return m_impl.Init(); };
bool TearDown() { return m_impl.TearDown(); };
private:
T_IMPL m_impl;
OidContainer<Oid,void *> m_dispatch_table;
};
} }
EDIT: self-contained minimal version
class test_skel
{
bool test_function()
{
return true;
}
};
template<typename T>
bool test_function()
{
}
template<typename T = test_skel>
class test
{
public:
test()
{
dispatch = (void *) & test_function<T>;
}
void * dispatch;
};
int main()
{
test<> t;
return 0;
}
There is no problem iff you don't use a void*, i.e.: http://www.ideone.com/eRgUG
However, if you insist on storing the pointer in a void*, then you need to take the address using a specific function pointer first and then cast - e.g.
bool (*temp)() = &test_function<T>;
dispatch = reinterpret_cast<void*>(temp); // YUCK
This gives the compiler enough context to generate the address for you.
Ahh - just saw DeadMG's answer, the function to generate the void* is neater...
Your self-contained example wouldn't compile for me with a strange error about overloaded functions, when there is no overloading going on, with MSVC. I did, however, manage to work around it.
class test_skel
{
bool test_function()
{
return true;
}
};
template<typename T> void* to_void_pointer(T t) {
return reinterpret_cast<void*>(t);
}
template<typename T>
bool test_function()
{
return true;
}
template<typename T = test_skel>
class test
{
public:
test()
{
dispatch = to_void_pointer(&test_function<T>);
}
void * dispatch;
};
int main()
{
test<> t;
return 0;
}
This compiles cleanly. I suspect that whatever behaviour you're seeing and I saw is a compiler error.
I have a map which represents a configuration. It's a map of std::string and boost::any.
This map is initialized at the start and I'd like the user to be able to override these options on the command line.
What I'd love to do is build the program options from this map using the options_description::add_option() method. However, it takes a template argument po::value<> whereas all I have is boost::any.
So far, I just have the shell of the code. m_Config represents my configuration class, and getTuples() returns a std::map<std::string, Tuple>. TuplePair is a typedef of std::pair<std::string, Tuple> and the Tuple contains the boost::any I am interested in.
po::options_description desc;
std::for_each(m_Config.getTuples().begin(),
m_Config.getTuples().end(),
[&desc](const TuplePair& _pair)
{
// what goes here? :)
// desc.add_options() ( _pair.first, po::value<???>, "");
});
Is there a way to build it this way, or do I need to resort to doing it myself?
Thanks in advance!
boost::any is not applicable to your problem. It performs the most basic form of type erasure: storage and (type-safe) retrieval, and that's it. As you've seen, no other operations can be performed. As jhasse points out, you could just test every type you want to support, but this is a maintenance nightmare.
Better would be to expand upon the idea boost::any uses. Unfortunately this requires a bit of boiler-plate code. If you'd like to try it, there's a new Boost library being discussed right now on the mailing list (titled "[boost] RFC: type erasure") that is essentially a generalized type erasure utility: you define the operations you'd like your erased type to support, and it generates the proper utility type. (It can simulate boost::any, for example, by requiring the erased type be copy-constructible and type-safe, and can simulate boost::function<> by additionally requiring the type be callable.)
Aside from that, though, your best option is probably to write such a type yourself. I'll do it for you:
#include <boost/program_options.hpp>
#include <typeinfo>
#include <stdexcept>
namespace po = boost::program_options;
class any_option
{
public:
any_option() :
mContent(0) // no content
{}
template <typename T>
any_option(const T& value) :
mContent(new holder<T>(value))
{
// above is where the erasure happens,
// holder<T> inherits from our non-template
// base class, which will make virtual calls
// to the actual implementation; see below
}
any_option(const any_option& other) :
mContent(other.empty() ? 0 : other.mContent->clone())
{
// note we need an explicit clone method to copy,
// since with an erased type it's impossible
}
any_option& operator=(any_option other)
{
// copy-and-swap idiom is short and sweet
swap(*this, other);
return *this;
}
~any_option()
{
// delete our content when we're done
delete mContent;
}
bool empty() const
{
return !mContent;
}
friend void swap(any_option& first, any_option& second)
{
std::swap(first.mContent, second.mContent);
}
// now we define the interface we'd like to support through erasure:
// getting the data out if we know the type will be useful,
// just like boost::any. (defined as friend free-function)
template <typename T>
friend T* any_option_cast(any_option*);
// and the ability to query the type
const std::type_info& type() const
{
return mContent->type(); // call actual function
}
// we also want to be able to call options_description::add_option(),
// so we add a function that will do so (through a virtual call)
void add_option(po::options_description desc, const char* name)
{
mContent->add_option(desc, name); // call actual function
}
private:
// done with the interface, now we define the non-template base class,
// which has virtual functions where we need type-erased functionality
class placeholder
{
public:
virtual ~placeholder()
{
// allow deletion through base with virtual destructor
}
// the interface needed to support any_option operations:
// need to be able to clone the stored value
virtual placeholder* clone() const = 0;
// need to be able to test the stored type, for safe casts
virtual const std::type_info& type() const = 0;
// and need to be able to perform add_option with type info
virtual void add_option(po::options_description desc,
const char* name) = 0;
};
// and the template derived class, which will support the interface
template <typename T>
class holder : public placeholder
{
public:
holder(const T& value) :
mValue(value)
{}
// implement the required interface:
placeholder* clone() const
{
return new holder<T>(mValue);
}
const std::type_info& type() const
{
return typeid(mValue);
}
void add_option(po::options_description desc, const char* name)
{
desc.add_options()(name, po::value<T>(), "");
}
// finally, we have a direct value accessor
T& value()
{
return mValue;
}
private:
T mValue;
// noncopyable, use cloning interface
holder(const holder&);
holder& operator=(const holder&);
};
// finally, we store a pointer to the base class
placeholder* mContent;
};
class bad_any_option_cast :
public std::bad_cast
{
public:
const char* what() const throw()
{
return "bad_any_option_cast: failed conversion";
}
};
template <typename T>
T* any_option_cast(any_option* anyOption)
{
typedef any_option::holder<T> holder;
return anyOption.type() == typeid(T) ?
&static_cast<holder*>(anyOption.mContent)->value() : 0;
}
template <typename T>
const T* any_option_cast(const any_option* anyOption)
{
// none of the operations in non-const any_option_cast
// are mutating, so this is safe and simple (constness
// is restored to the return value automatically)
return any_option_cast<T>(const_cast<any_option*>(anyOption));
}
template <typename T>
T& any_option_cast(any_option& anyOption)
{
T* result = any_option_cast(&anyOption);
if (!result)
throw bad_any_option_cast();
return *result;
}
template <typename T>
const T& any_option_cast(const any_option& anyOption)
{
return any_option_cast<T>(const_cast<any_option&>(anyOption));
}
// NOTE: My casting operator has slightly different use than
// that of boost::any. Namely, it automatically returns a reference
// to the stored value, so you don't need to (and cannot) specify it.
// If you liked the old way, feel free to peek into their source.
#include <boost/foreach.hpp>
#include <map>
int main()
{
// (it's a good exercise to step through this with
// a debugger to see how it all comes together)
typedef std::map<std::string, any_option> map_type;
typedef map_type::value_type pair_type;
map_type m;
m.insert(std::make_pair("int", any_option(5)));
m.insert(std::make_pair("double", any_option(3.14)));
po::options_description desc;
BOOST_FOREACH(pair_type& pair, m)
{
pair.second.add_option(desc, pair.first.c_str());
}
// etc.
}
Let me know if something is unclear. :)
template<class T>
bool any_is(const boost::any& a)
{
try
{
boost::any_cast<const T&>(a);
return true;
}
catch(boost::bad_any_cast&)
{
return false;
}
}
// ...
po::options_description desc;
std::for_each(m_Config.getTuples().begin(),
m_Config.getTuples().end(),
[&desc](const TuplePair& _pair)
{
if(any_is<int>(_pair.first))
{
desc.add_options() { _pair.first, po::value<int>, ""};
}
else if(any_is<std::string>(_pair.first))
{
desc.add_options() { _pair.first, po::value<std::string>, ""};
}
else
{
// ...
}
});
// ...
If you have more than a handful of types consider using typelists.