I am trying to get a better understanding of std::enable_if in C++11 and have been trying to write a minimal example: a class A with a member function void foo() that has different implementations based on the type T from the class template.
The below code gives the desired result, but I am not understanding it fully yet. Why does version V2 work, but not V1? Why is the "redundant" type U required?
#include <iostream>
#include <type_traits>
template <typename T>
class A {
public:
A(T x) : a_(x) {}
// Enable this function if T == int
/* V1 */ // template < typename std::enable_if<std::is_same<T,int>::value,int>::type = 0>
/* V2 */ template <typename U=T, typename std::enable_if<std::is_same<U,int>::value,int>::type = 0>
void foo() { std::cout << "\nINT: " << a_ << "\n"; }
// Enable this function if T == double
template <typename U=T, typename std::enable_if<std::is_same<U,double>::value,int>::type = 0>
void foo() { std::cout << "\nDOUBLE: " << a_ << "\n"; }
private:
T a_;
};
int main() {
A<int> aInt(1); aInt.foo();
A<double> aDouble(3.14); aDouble.foo();
return 0;
}
Is there a better way to achieve the desired result, i.e. for having different implementations of a void foo() function based on a class template parameter?
I know this wont fully answer your question, but it might give you some more ideas and understanding of how you can use std::enable_if.
You could replace your foo member functions with the following and have identical functionality:
template<typename U=T> typename std::enable_if<std::is_same<U,int>::value>::type
foo(){ /* enabled when T is type int */ }
template<typename U=T> typename std::enable_if<std::is_same<U,double>::value>::type
foo(){ /* enabled when T is type double */ }
A while back I gained a pretty good understanding of how enable_if works, but sadly I have forgotten most of its intricacies and just remember the more practical ways to use it.
As for the first question: why V1 doesn't work? SFINAE applies only in overload resolution - V1 however causes error at the point where type A is instantiated, well before foo() overload resolution.
I suppose there are lot's of possible implementations - which is the most appropriate depends on an actual case in question. A common approach would be to defer the part of A that's different for different template types to a helper class.
template <typename T>
class A_Helper;
template <>
class A_Helper<int> {
public:
static void foo( int value ){
std::cout << "INT: " << value << std::endl;
}
};
template <>
class A_Helper<double> {
public:
static void foo( double value ){
std::cout << "DOUBLE: " << value << std::endl;
}
};
template <typename T>
class A {
public:
A( T a ) : a_(a)
{}
void foo(){
A_Helper<T>::foo(a_);
}
private:
T a_;
};
The rest of A can be declared only once in a generic way - only the parts that differ are deferred to a helper. There is a lot of possible variations on that - depending on your requirements...
Related
I have a class and need to validate that it's function calls are being called w/ the right parameters. The function signature is always the same (sans 1 argument type). So, naturally I went for a templated approach. So generally the validation policy would have a template parameter per data type it could handle:
using P = Policy<int, double, UserDefined>
Or something of that ilk.
I got it to compile, but the caveat is that if double and int (or anything a double can convert to actually) are both template parameters, the double will be implicitly converted.
The policy looks like this:
template <typename... T>
class BasicValidationPolicy { };
template <>
class BasicValidationPolicy<>
{
public:
void RegisterSetHandler();
};
template <typename T, typename... Rest>
class BasicValidationPolicy<T, Rest...> : public BasicValidationPolicy<Rest...>
{
public:
using SetHandler = std::function<void(int, T)>;
void RegisterSetHandler(const SetHandler& handler)
{
m_setHandler = handler;
}
void Set(int n, const T& val) {
if (m_setHandler) {
m_setHandler(n, val);
}
}
private:
SetHandler m_setHandler{nullptr};
};
The class that uses it...
template <typename ValidatorPolicy>
class MyClass : public ValidatorPolicy {
public:
void OnSetInt(int n, int64_t v)
{
ValidatorPolicy::Set(n, v);
}
void OnSetDouble(int n, double d)
{
ValidatorPolicy::Set(n, d);
}
};
Usage:
int main()
{
using Policy = BasicValidationPolicy<int64_t, double>; // doesn't work
MyClass<Policy> m;
m.Policy::RegisterSetHandler([](int i, double value) {
// by this point value is an int64_t
std::cout << "Got double " << i << ", " << value << "\n";
});
double d{35.2135};
m.OnSetDouble(1, d);
}
To boot, doing this does work
using Policy = BasicValidationPolicy<double, int64_t>;
So I guess I'm missing something about the template deduction. Looks like it tries to match double against std::int64_t says "meh, good enough", and moves on. Nice to know a way around it (kind of) but that looks like it would be very tricky to maintain.
It's complicated...
First of all: you have a recursive template class, BasicValidationPolicy, where you define two methods and you want that all methods, for all recursion steps of the class, are available.
Unfortunately, the definition of the methods in the derived classes hide the method in base classes.
To un-hide the inherited methods, you have to explicitly add a pair of using
using BasicValidationPolicy<Rest...>::Set;
using BasicValidationPolicy<Rest...>::RegisterSetHandler;
At this point, the code doesn't compile because you need a Set() and a RegisterSetHandler() in the ground case class. You have declared a dummy RegisterSetHandler() but not a dummy Set(). You have to add one, so the ground case become
template <>
class BasicValidationPolicy<>
{
public:
void RegisterSetHandler();
void Set();
};
Now your MyClass<Policy> object expose two RegisterSetHandler() methods (before only one): one receiving a std::function<void(int, std::int64_t)>, the other (before hidden) receiving a std::function<void(int, double)>.
But when you pass a lambda, you have a chicken-and-egg problem: the lambda can be converted to a std::function but isn't a std::function. So can't be used to deduce the template parameters of std::function because the types are to be known before to deduce them.
A possible solution is impose a lambda/std::function conversion in the call
// ..........................VVVVVVVVVVVVVV
m.Policy::RegisterSetHandler(std::function{[](int i, double value) {
// by this point value is an int64_t
std::cout << "Got double " << i << ", " << value << "\n";
}});
// ...........................^
using also the template deduction guides introduced in C++17.
So your code become
#include <iostream>
#include <functional>
template <typename... T>
class BasicValidationPolicy { };
template <>
class BasicValidationPolicy<>
{
public:
void RegisterSetHandler();
void Set();
};
template <typename T, typename... Rest>
class BasicValidationPolicy<T, Rest...> : public BasicValidationPolicy<Rest...>
{
public:
using SetHandler = std::function<void(int, T)>;
using BasicValidationPolicy<Rest...>::Set;
using BasicValidationPolicy<Rest...>::RegisterSetHandler;
void RegisterSetHandler(const SetHandler& handler)
{
m_setHandler = handler;
}
void Set(int n, const T& val) {
if (m_setHandler) {
m_setHandler(n, val);
}
}
private:
SetHandler m_setHandler{nullptr};
};
template <typename ValidatorPolicy>
class MyClass : public ValidatorPolicy {
public:
void OnSetInt(int n, int64_t v)
{
ValidatorPolicy::Set(n, v);
}
void OnSetDouble(int n, double d)
{
ValidatorPolicy::Set(n, d);
}
};
int main ()
{
using Policy = BasicValidationPolicy<int64_t, double>; // doesn't work
MyClass<Policy> m;
m.Policy::RegisterSetHandler(std::function{[](int i, double value) {
std::cout << "Got double " << i << ", " << value << "\n";
}});
double d{35.2135};
m.OnSetDouble(1, d);
}
There's a small alternative to the recursive definition that might be easier to work with...
template<typename T>
class ValidationPolicy {
// Set/Register/etc
};
template <typename... Ts>
class BasicValidationPolicy : public ValidationPolicy<Ts>... {
public:
using ValidationPolicy<Ts>::Set...;
using ValidationPolicy<Ts>::RegisterSetHandler...;
};
This can have some impacts on compile time and other aspects of development, though most likely relatively minor. For instance, if you have dozens of classes used in hundreds of different policy combinations in your app, the recursive definition will lead to many more distinct types and larger binaries to support that. For example, in the recursive definition BasicValidationPolicy<T1, T2, T3> and BasicValidationPolicy<T3, T2, T1> being used would generate 7 distinct types in a hierarchy (the empty one is shared in both expansions). The same thing in the flatter hierarchy would be 5 distinct types - one for each of T1, T2, T3 and one for each combination. Adding in BasicValidationPolicy<T2, T3, T1> would add 3 more types recursively but 1 more type in the flat form.
The answer from #max66 isn't wrong, just something else to think about.
I have a case where I need to have a forwarding function defined before a template base function is defined/declared. However, if I call the forwarding function (fwd) that in turn calls the base function test, it says that the base template function is not visible (see the code below). However, if test is called directly, everything works.
So my question is this, is it possible to have the forwarding function make a call to a base template function that is defined later in the compilation unit (before it is used but after the forwarding function)? If not, do I have any options to work around this? I would like to avoid a forward declaration before fwd as that would make use of the library I am developing harder. I think if I could force fwd to be inline it would solve the problem but I have no way of doing that unless a macro is used.
#include <iostream>
#include <vector>
template<typename T, std::enable_if_t<std::is_scalar<T>::value, int> = 0>
void test(const T& t)
{
std::cout << "Scalar" << std::endl;
}
template<typename T>
void fwd(T&& t)
{
test(std::forward<T>(t));
}
template<typename T>
void test(const std::vector<std::vector<T>>& t)
{
std::cout << "vector vector of T" << std::endl;
}
int main(int argc, const char * argv[]) {
test(1); //OK, prints Scalar
fwd(1); //OK, prints Scalar
test(std::vector<std::vector<int>>()); //OK, prints vector vector of T
// Causes compile error: Call to function 'test' that is neither visible in the template definition
// nor found by argument dependent lookup
fwd(std::vector<std::vector<int>>());
return 0;
}
The name test in fwd is a dependent name. It will be resolved into two steps:
Non-ADL lookup examines function declarations ... that are visible from the template definition context.
ADL examines function declarations ... that are visible from either the template definition context or the template instantiation context.
Given that the relative order of test and fwd should not be changed, one possible solution is to use a fake tag struct in the namespace to activate ADL:
namespace my_namespace
{
struct Tag {};
template<typename T, std::enable_if_t<std::is_scalar<T>::value, int> = 0>
void test(const T& t, Tag = Tag{}) {
std::cout << "Scalar" << std::endl;
}
template<typename T>
void fwd(T&& t) {
test(std::forward<T>(t), Tag{});
}
template<typename T>
void test(const std::vector<std::vector<T>>& t, Tag = Tag{}) {
std::cout << "vector vector of T" << std::endl;
}
}
int main() {
my_namespace::test(std::vector<std::vector<int>>()); // OK
my_namespace::fwd(std::vector<std::vector<int>>()); // OK, too
}
Demo
Depending on what test overloads you have, another solution might be to wrap these functions into structs and use template specialization instead of function overloading:
template<class T>
struct Test {
static void op(const T& t) {
std::cout << "Scalar" << std::endl;
}
};
template<typename T>
void fwd(T&& t) {
Test<std::decay_t<T>>::op(std::forward<T>(t));
}
template<class T>
struct Test<std::vector<std::vector<T>>> {
static void op(const std::vector<std::vector<T>>& t) {
std::cout << "vector vector of T" << std::endl;
}
};
int main() {
fwd(1);
fwd(std::vector<std::vector<int>>());
}
Demo
While trying to solve Is it possible to tell if a class has hidden a base function in C++?, I generated this:
#include <type_traits>
#include <iostream>
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0
template<class T, class B, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f(T*) -> std::true_type;
template<class T, class B>
auto has_x_f(B*) -> std::false_type;
template<class T, class B>
using has_x = decltype(has_x_f<T, B>((T*)nullptr));
template<typename T>
struct A
{
void x() {}
static const bool x_hidden;
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
template <typename R, ENABLE_IF(std::is_same<T, R>::value && !x_hidden)>
void y(R value)
{
std::cout << "x() is not hidden" << std::endl;
}
//using t = std::integral_constant<bool, x_hidden>;
};
struct B : A<B>
{
void x() {}
};
struct C : A<C>
{
};
template<typename T>
const bool A<T>::x_hidden = has_x<T, A<T>>::value;
int main()
{
B b;
C c;
std::cout << "B: ";
std::cout << b.x_hidden << std::endl;
std::cout << "C: ";
std::cout << c.x_hidden << std::endl;
std::cout << "B: ";
b.y(b);
std::cout << "C: ";
c.y(c);
return 0;
}
Which outputs what I want:
B: 1
C: 0
B: x() is hidden
C: x() is not hidden
clang and gcc both compile and execute this "correctly", but vc++ doesn't (though I am aware that there are problems with it working properly with expressions similar to template <typename T> ... decltype(fn(std::declval<T>().mfn()))).
So my question is, is this considered valid or will it break later on? I'm also curious about the x_hidden being able to be used as a template parameter in the functions but not being able to use it in using t = std::integral_constant<bool, x_hidden>. Is that just because the template's type isn't fully declared at this point? If so, why did using it work for the function declarations?
If x_hidden is false, there is no template arguements for which this template function
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value) {
std::cout << "x() is hidden" << std::endl;
}
can be instantiated, so your program is ill formed no diagnostic required. This is a common hack, its illegality may be made clear or even legal at some point.
There may be a reason for using has_x_f instead of just directly initializing is_hidden with the is_same clause, but it isn't demonstrated in your code.
For any template specialization, there must be arguments which would make the instantiation valid. If there are not, the program is ill-formed no diagnostic required.
I believe this clause is in the standard to permit compilers to do more advanced checks on templates, but not require them.
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
the compiler is free to notice x_hidden is false, and say "it doesn't matter what is_same<T,R> is", and deduce that no template arguments could make this specialization valid. Then generate an error.
An easy hack is
template <class T2=T, class R,
ENABLE_IF(std::is_same<T2, R>::value && has_x<T2, A<T2>>::value)
>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
where we sneak another template argument in that equals T usually. Now, the compiler has to admit the possibility that T2 passes the has_x test, and that the passed argument is R. Users can bypass this by manually passing the "wrong" T2.
This may not solve everything. The standard is a bit tricky to read here, but one reading states that if within the body of y() we go and assume that our T itself has x(), we still violate the rule of the possibility of a valid template instantiation.
[temp.res] 14.6/8 (root and 1)
Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template [...] and the template is not instantiated, or
No valid specialization for
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
can be generated if x_hidden is false. The exitence of another overload is immaterial.
If you fix it using the T2 trick, the same rule holds if the body assumes T=T2.
Three are words in the standard that attempt to not cause the template to be instantiated in certain contexts, but I am unsure if that makes the above code well formed or not.
I tried compiling your code with the Intel C++ compiler(icpc (ICC) 17.0.2 20170213), and it would not compile with the following message:
main.cpp(30): error: expression must have a constant value
template <typename R, ENABLE_IF(std::is_same<T, R>::value && !x_hidden)>
^
/home/com/gcc/6.2.0/bin/../include/c++/6.2.0/type_traits(2512): error: class "std::enable_if<<error-constant>, int>" has no member "type"
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
^
detected during instantiation of type "std::enable_if_t<<error-constant>, int>" at line 30 of "main.cpp"
main.cpp(62): error: more than one instance of overloaded function "B::y" matches the argument list:
function template "void A<T>::y(R) [with T=B]"
function template "void A<T>::y(R) [with T=B]"
argument types are: (B)
object type is: B
b.y(b);
I was however able to compile the following with both the Intel compiler and GCC.
#include <iostream>
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0
template<class T, class B, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f(T*) -> std::true_type;
template<class T, class B>
auto has_x_f(B*) -> std::false_type;
template<class T, class B>
using has_x = decltype(has_x_f<T, B>((T*)nullptr));
template<class T>
class A
{
public:
T& self() { return static_cast<T&>(*this); }
void x() { }
template
< class TT = T
, typename std::enable_if<has_x<TT, A<TT> >::value, int>::type = 0
>
void y()
{
std::cout << " have x hidden " << std::endl;
// if you are so inclined, you can call x() in a "safe" way
this->self().x(); // Calls x() from class "Derived" (Here class B)
}
template
< class TT = T
, typename std::enable_if<!has_x<TT, A<TT> >::value, int>::type = 0
>
void y()
{
std::cout << " does not have x hidden " << std::endl;
// if you are so inclined, you can call x() in a "safe" way
this->self().x(); // Calls x() from class "Base" (Here class A)
}
};
class B : public A<B>
{
public:
void x() { }
};
class C : public A<C>
{
};
int main()
{
B b;
C c;
b.y();
c.y();
return 0;
}
I am not aware whether or not this is incorrect according to the standard however, but as I see it you do not run into the problem mentioned in one of the other answers, that you have a template that cannot be instantiated.
EDIT: I was able to get to compile on MSVC 2017 by some "old-times" template metaprogramming tricks, and using classes instead of functions.
If I use this implementation of has_x instead it compiles:
template<class T, bool>
struct has_x_impl;
template<class T>
struct has_x_impl<T, true>: std::true_type
{
};
template<class T>
struct has_x_impl<T, false>: std::false_type
{
};
template<class T>
using has_x = has_x_impl<T, std::is_same<void(T::*)(), decltype(&T::x)>::value>;
Full code on Wandbox here.
I had a bit of a code clean-up (got rid of the out-of-line x_hidden declaration) and ended up with the following. I also fixed it slightly based on #Yakk's answer above, to avoid [temp.res]/8 invalidating it.
#include <type_traits>
#include <iostream>
#include <cassert>
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0
template<class T, class Base, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f() -> std::true_type;
template<class T, class Base, ENABLE_IF(std::is_same<void(Base::*)(), decltype(&T::x)>::value)>
auto has_x_f() -> std::false_type;
template<class T, class Base>
using has_x = decltype(has_x_f<T, Base>());
template<typename T>
struct A
{
void x() {}
static bool constexpr x_hidden() {
return has_x<T, A<T>>::value;
}
void y()
{
assert(x_hidden() == y_<T>(nullptr) );
}
void y2()
{
if constexpr(x_hidden()) {
typename T::BType i = 1;
(void)i;
} else {
typename T::CType i = 1;
(void)i;
}
}
private:
template <typename R, typename T2=T, ENABLE_IF(A<T2>::x_hidden())>
static bool y_(R*)
{
std::cout << "x() is hidden" << std::endl;
return true;
}
template <typename R, typename T2=T, ENABLE_IF(!A<R>::x_hidden())>
static bool y_(T*)
{
std::cout << "x() is not hidden" << std::endl;
return false;
}
};
struct B : A<B>
{
void x() {}
using BType = int;
};
static_assert(std::is_same<decltype(&B::x), void(B::*)()>::value, "B::x is a member of B");
struct C : A<C>
{
using CType = int;
};
static_assert(std::is_same<decltype(&C::x), void(A<C>::*)()>::value, "C::x is a member of A<C>");
int main()
{
B b;
C c;
std::cout << "B: ";
std::cout << B::x_hidden() << std::endl;
std::cout << "C: ";
std::cout << C::x_hidden() << std::endl;
std::cout << "B: ";
b.y();
b.y2();
std::cout << "C: ";
c.y();
c.y2();
return 0;
}
Live demo on wandbox -- gcc and clang are both happy with it.
MSVC 2017 complained
error C2064: term does not evaluate to a function taking 0 arguments
for both uses of A<T2>::x_hidden(), when instantiating A<B> for B to inherit from.
MSVC 2015 gave the same complaint, and then suffered an Internal Compiler Error. ^_^
So I think this is valid, but exercises MSVC's constexpr or template instantiation machinery in unpleasant ways.
Per the example in [expr.unary.op]/3, the type of &B::x is void (B::*)(), and the type of &C::x is void (A<C>::*)(). So the first has_x_f() will be present when T is B, and the second has_x_f() will be present when T is C and Base is A<C>.
Per [temp.inst]/2, instantiating the class instantiates declarations but not definitions of the members. Per [temp.inst]/3 and 4, member function definitions (including template functions) are not instantiated until required.
Our declarations here are currently different, as the use of R and T2 mean the compiler cannot determine the truth or falsehood of either size of the &&.
The use of the different parameter types helps MSVC, which would otherwise see them as redefinitions of the same template member template function. My reading of [temp.inst]/2 says this is not needed, as they're only redefintions when we instantiate them, and they cannot be instantiated at the same time. Because we use A<T2>::x_hidden() and !A<R>::x_hidden(), the compiler cannot know that they are mutually exclusive at this time. I don't think it's necessary to do that to avoid [temp.res]/8, simply using A<R>::x_hidden() seems safe-enough to me. This was also to ensure that in the two templates, R as actually used.
From there on, it's pretty easy. y() shows we have the right values coming from both paths.
Depend on your use-case, you could use if constexpr with x_hidden() to avoid all the template magic in y_(), per y2() above.
This avoids the issue with [temp.res]/8 described in #Yakk's answer, as the problematic clause [temp.res]/8.1 is that the template is ill-formed if
no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, [...]
So as long as you instantiate A<T>::y2() for some T, then you're not subject to this clause.
The y2() approach has the advantage of working with MSVC2017, as long as you pass in the "/std:c++latest" compiler flag.
I have looked around a while for a solution to this, however, I might not know the exact definition or language syntax of what I am trying to accomplish, so I decided to post.
I have certain objects/structs like so:
struct A
{
char myChar;
bool hasArray = false;
};
template <uint8_t ARRAY_LEN>
struct AA : public A
{
hasArray = true;
uint8_t myArray[ARRAY_LEN];
};
I want to create a generic function that can take in both of these object types and to perform common work as well as specific work for the derived struct AA. Something like the following:
template <typename T>
void func(T (&m))
{
if (T.hasArray)
{
// do some processing with m.myArray
std::cout << sizeof(m.myArray) << std::endl;
// ...
}
// common processing
std::cout << "myChar: " << m.myChar << std::endl;
};
I want to be able to call the function like so:
A a;
AA aa;
func(a); // compiler error, this would not work as no array member
func(aa); // this works
Granted this is just an example that illustrates my intent, but it sums up what I would like to do. The actual code is a lot more complex and involved many more objects. I know I can overload, but I want to know if there is a way to do it with one generic function? Also note that I understand why the compiler complains with the sample code I would like to know if there is a workaround or some other c++ functionality that I am missing. I would not like to do any type casting...
- Using c++11 and GCC 4.8.5
This is a C++14 feature of reasonably large complexity. C++17 introduced if constexpr to make this easier; but it is doable.
template<std::size_t I>
using index_t=std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};
constexpr inline index_t<0> dispatch_index() { return {}; }
template<class B0, class...Bs,
std::enable_if_t<B0::value, int> =0
>
constexpr index_t<0> dispatch_index( B0, Bs... ) { return {}; }
template<class B0, class...Bs,
std::enable_if_t<!B0::value, int> =0
>
constexpr auto dispatch_index( B0, Bs... ) {
return index< 1 + dispatch_index( decltype(Bs){}...) >;
}
template<class...Bs>
auto dispatch( Bs... ) {
using I = decltype(dispatch_index( decltype(Bs){}... ));
return [](auto&&...args)->decltype(auto){
return std::get<I::value>( std::make_tuple(decltype(args)(args)..., [](auto&&...){}) );
};
}
dispatch( some_test ) returns a lambda that takes auto&&.... It in turn returns the first argument if some_test is of a true-like-type, and the second argument (or [](auto&&...){} if no second argument) if some_test is of a false-like-type.
We then write code to detect your myArray.
namespace details {
template<template<class...>class Z, class=void, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply<Z, void, Ts...>::type;
template<class T>
using myArray_type = decltype( std::declval<T>().myArray );
template<class T>
using has_myArray = can_apply< myArray_type, T >;
and has_myArray<T> is true-like if T has a member .myArray.
We hook these together
dispatch( has_myArray<T>{} )(
[&](auto&& m) {
// do some processing with m.myArray
std::cout << sizeof(m.myArray) << std::endl;
// ...
}
)( m );
and now the lambda in the middle is run if and only if m.myArray is valid.
More complex tests that check for more than just existence can be written, but the above is usually sufficient.
In a non-C++11 compiler like MSVC 2015, replace
std::enable_if_t<B0::value, int> =0
and
std::enable_if_t<!B0::value, int> =0
with
class = std::enable_if_t<B0::value>
and
class = std::enable_if_t<!B0::value>, class=void
respectively. Yes, these are uglier. Go talk to MSVC compiler team.
If your compiler lacks C++14, you'll have to write your own void_t and either write your own enable_if_t or use the ugly longer version using enable_if.
In addition, the template variable index is illegal in C++11. Replace index<blah> with index_t<blah>{}.
The lack of auto&& lambdas makes the above very painful; you may have to convert the lambda to an out-of-line function object. However, auto lambdas where one of the first C++14 features people implemented, often before they finished C++11.
The above code is solid designed, but may contain typos.
Overloading works just fine in your case if you don't want to modify your instances:
#include<iostream>
#include<cstdint>
struct A
{
char myChar;
};
template <uint8_t ARRAY_LEN>
struct AA : public A
{
uint8_t myArray[ARRAY_LEN];
};
void func(const A &m)
{
std::cout << "myChar: " << m.myChar << std::endl;
};
template <uint8_t AL>
void func(const AA<AL> &m)
{
std::cout << sizeof(m.myArray) << std::endl;
func(static_cast<const A &>(m));
}
int main() {
func(A{});
func(AA<1>{});
}
If you still want to go with a template function and a bit of sfinae, I would probably use something like this instead:
#include<iostream>
#include<cstdint>
struct A
{
char myChar;
};
template <uint8_t ARRAY_LEN>
struct AA : public A
{
uint8_t myArray[ARRAY_LEN];
};
void func(A &m)
{
std::cout << "myChar: " << m.myChar << std::endl;
}
template <typename T>
auto func(T &m) -> decltype(m.myArray, void())
{
std::cout << sizeof(m.myArray) << std::endl;
A &a = m;
func(a);
}
int main() {
AA<1> aa{};
A a{};
func(a);
func(aa);
}
Note that in both cases you don't actually require the hasArray member data.
there is a way to do it with one generic function?
I don't think so, because if you insert a sizeof(m.myArray) in this function, you can't call it with a type without a myArray member. Even if it is in a part of code that, run time, isn't executed, because the compiler need to compile it.
But, if I understand correctly, your hasArray say if your struct has a myArray member or not. So I suppose you can transform it in a static constexpr member, as follows
struct A
{
static constexpr bool hasArray { false };
char myChar { 'z' };
};
template <uint8_t ARRAY_LEN>
struct AA : public A
{
static constexpr bool hasArray { true };
uint8_t myArray[ARRAY_LEN];
};
Now, in func(), you can call a second function, func2(), to choose the two cases: myArray or not myArray. You can use SFINAE for this but (IMHO) is better tag dispatching, in this case. So you can transform your hasArray in a different type
template <typename T>
void func2 (T const & m, std::true_type const &)
{ std::cout << sizeof(m.myArray) << ", "; }
template <typename T>
void func2 (T const &, std::false_type const &)
{ }
template <typename T>
void func(T (&m))
{
func2(m, std::integral_constant<bool, T::hasArray>{});
// common processing
std::cout << "myChar: " << m.myChar << std::endl;
}
Now you can call func() with both types
int main()
{
A a;
AA<12U> aa;
func(a); // print myChar: z
func(aa); // print 12, myChar: z
}
Remember to include type_traits and iostream.
So I was looking for ways to check whether a function with a particular argument exists. I have a templated method which relies on an external function (external from the class) to do the job:
template <class Moo>
void exportDataTo(Moo& ret){
extended_solid_loader(ret, *this);
}
At multiple points in the project I have macros which define extended_solid_loader for different types, but now I want to be able to use a default function if extended_solid_loader hasn't been defined for that particular class type.
I came across this:
Is it possible to write a template to check for a function's existence?
but it seems a little different, in that I'm not checking for a method, but rather a definition of a function with a particular argument type.
Is this possible right now?
You can just provide a function template for extended_solid_loader providing a default implementation, and users who want to use something other than the default implementation just specialize that.
template<class T>
void extended_solid_loader(T & ret, SomeClass & obj) {
// default implementation here
}
template<>
void extended_solid_loader<MooClass>(MooClass & ret, SomeClass & obj) {
// special implementation for MooClass here
}
You don't actually have to do anything particularly special. Just make sure there's a version of that function available to the template and let ADL do the dirty work. Check out this example:
#include <iostream>
namespace bob {
struct X {};
void f(X const&) { std::cout << "bob::f\n"; }
}
namespace ed {
template < typename T >
void f(T const&) { std::cout << "ed::f\n"; }
template < typename T >
struct test
{
void doit() // not called f and no other member so named.
{ f(T()); }
};
}
int main()
{
ed::test<int> test1;
ed::test<bob::X> test2;
test1.doit();
test2.doit();
std::cin.get();
}
Works without the namespace stuff too (non-templates have preference). I just used that to show that ADL will pick it up when you do.
Your original question was interesting. Found a way to do it in C++0x:
template < typename T >
struct fun_exists
{
typedef char (&yes) [1];
typedef char (&no) [2];
template < typename U >
static yes check(decltype(f(U()))*);
template < typename U >
static no check(...);
enum { value = sizeof(check<T>(0)) == sizeof(yes) };
};
void f(double const&) {}
struct test {};
#include <iostream>
int main()
{
std::cout << fun_exists<double>::value << std::endl;
std::cout << fun_exists<test>::value << std::endl;
std::cin.get();
}