I'm setting up a variadic template function to be able to call various function overloads on a specific series of classes. So far, I've been able to "break" the compilation when an unsupported class is passed to the function, but I'd like to be able to provide a valid fallback to handle the "unsupported" scenario at runtime.
The current implementation goes like this :
struct ClassA {};
struct ClassB {};
struct ClassC {};
template<typename T> struct is_my_class : std::false_type {};
template<> struct is_my_class<ClassA> : std::true_type {};
template<> struct is_my_class<ClassB> : std::true_type {};
template<typename T>
constexpr bool is_my_class_v = is_my_class<T>::value;
void runOverload(ClassA c) { printf("ClassA overload\n"); }
void runOverload(ClassB c) { printf("ClassB overload\n"); }
template<typename T, typename = std::enable_if_t<is_my_class_v<T>>>
void run(T myClass)
{
runOverload(myClass);
};
template<typename T, typename... Ts>
void run(T myClass, Ts... classesLeft)
{
run(myClass);
run(classesLeft...);
};
int main()
{
ClassA a;
ClassB b;
ClassC c;
run(a, b); // works
run(c); // does not compile
}
Here, the two most promising (but still failing) attempts I've made to have a fallback for run:
1 - Adding a simple ! in front of is_my_class<T>, giving me the following error : error C2995: 'void run(T)': function template has already been defined
template<typename T, typename = std::enable_if_t<!is_my_class_v<T>>>
void run(T myClass)
{
printf("Not supported\n");
};
2 - Making a more "primary" template definition, which yields a sad but obvious : error C2668: 'run': ambiguous call to overloaded function
template<typename T>
void run(T myClass)
{
printf("Not supported\n");
};
EDIT
I forgot to specify I was looking for a solution also compatible with C++11/14
You could avoid enable_if entirely, and just use a compile-time if to to decide what code to execute:
template<typename T>
void run(T myClass)
{
if constexpr (is_my_class_v<T>)
runOverload(myClass);
else
printf("Not supported\n");
}
Here's a demo.
Even though I'd recommend #cigien 's solution if c++17 is avaiable, I would like to add, that the ambiguity problem can be mitigated pre c++17 by changing the type of the enable_if template argument, and hence changing the signature of the "fallback function". The following code should work fine:
template<typename T, std::enable_if_t<!is_my_class_v<T>, int> = 0>
//template<typename T>
void run(T myClass) {
printf("error\n");
};
Full code available on CompilerExplorer, tested on GCC and Clang trunk.
I'd also like to add, that in all use cases I can imagine, it is better to have a compile time error (which you could for instance achieve by using a static assertion instead of SFINAE).
Here you can read about why the function delcaration is not ambiguous, even when using two "ints" as template arguments.
I wrote following code and it works. I think you just messed up with syntax of SFINAE.
#include <iostream>
#include <type_traits>
struct ClassA {};
struct ClassB {};
struct ClassC {};
template<typename T> struct is_my_class : std::false_type {};
template<> struct is_my_class<ClassA> : std::true_type {};
template<> struct is_my_class<ClassB> : std::true_type {};
template<typename T>
constexpr bool is_my_class_v = is_my_class<T>::value;
void runOverload(ClassA c) { printf("ClassA overload\n"); }
void runOverload(ClassB c) { printf("ClassB overload\n"); }
template<typename T, std::enable_if_t<is_my_class_v<T>,int> = 0>
void run(T myClass)
{
runOverload(myClass);
};
template<typename T, std::enable_if_t<not is_my_class_v<T>,int> = 0>
void run(T myClass)
{
printf("Not supported\n");
};
// wrote an extra SFINEA here to ensure that Ts aren't empty - else it might be an ODR issue despite the fact that it compiles
template<typename T, typename... Ts, std::enable_if_t<sizeof...(Ts) != 0,int> = 0>
void run(T myClass, Ts... classesLeft)
{
run(myClass);
run(classesLeft...);
};
int main()
{
ClassA a;
ClassB b;
ClassC c;
run(a, b);
run(c);
}
It prints
ClassA overload
ClassB overload
Not supported
Have a fallback runOverload template
template<typename T>
void runOverload(T myClass)
{
printf("Not supported\n");
};
Related
Say you have the following code:
class A {
bool _attribute1;
};
// Arbitrarily using std::string, not the point of this question
std::string serialize(const A&);
Now a developer adds a new bool _attribute2 to class A and forgets to update the serialize function, which leads to a bug at runtime. (Already been there ?)
Is there a way to turn this issue into compile-time error ? As C++ doesn't support reflection, I have the feeling this is impossible, but I may be missing something.
If you are using c++1z you could make use of structured binding:
struct S {
bool b;
//bool c; // causes error
};
int main() {
S s;
auto [x] = s;
(void)x;
}
[live demo]
The following one should work with C++11.
A bit tricky indeed, it's based on a comment of #SamVarshavchik:
#include<cstddef>
#include<functional>
template<std::size_t> struct Int { int i; };
template<std::size_t> struct Char { char c; };
template<std::size_t> struct Bool { bool c; };
template<typename, template<std::size_t> class...>
struct Base;
template<template<std::size_t> class... T, std::size_t... I>
struct Base<std::index_sequence<I...>, T...>: T<I>... {};
template<template<std::size_t> class... T>
struct Check final: Base<std::make_index_sequence<sizeof...(T)>, T...> {};
class A final {
bool _attribute1;
bool _attribute2;
private:
char _attribute3;
// int _attribute4;
};
void serialize(const A &) {
static_assert(sizeof(A) == sizeof(Check<Bool, Bool, Char>), "!");
// do whatever you want here...
}
int main() {
serialize(A{});
}
The basic idea is to list all the types of the data members and define a new type from them with a mixin. Then it's a matter of putting a static_assert in the right place.
Note that private data members are taken in consideration too.
There exist some corner cases that could break it, but maybe it can work for your real code.
As a side note, it can be further simplified if C++14 is an option:
#include<cstddef>
template<typename... T>
constexpr std::size_t size() {
std::size_t s = 0;
std::size_t _[] = { s += sizeof(T)... };
(void)_;
return s;
}
class A final {
bool _attribute1;
bool _attribute2;
private:
char _attribute3;
// int _attribute4;
};
void serialize(const A &) {
static_assert(sizeof(A) == size<bool, bool, char>(), "!");
// ...
}
int main() {
serialize(A{});
}
If you are doomed to use c++11 and still you are interested in serializing only the public fields you could create trait testing if the type can be constructed using list initialization with a given parameters types but not even one more (of any type):
#include <type_traits>
struct default_param {
template <class T>
operator T();
};
template <class T, class...>
using typer = T;
template <class, class, class... Args>
struct cannot_one_more: std::true_type {};
template <class Tested, class... Args>
struct cannot_one_more<typer<void, decltype(Tested{std::declval<Args>()..., default_param{}})>, Tested, Args...>: std::false_type {
};
template <class...>
struct is_list_constructable: std::false_type {};
template <class Tested, class... Args>
struct is_list_constructable<Tested(Args...)>: is_list_constructable<void, Tested, Args...> { };
template <class Tested, class... Args>
struct is_list_constructable<typer<void, decltype(Tested{std::declval<Args>()...}), typename std::enable_if<cannot_one_more<void, Tested, Args...>::value>::type>, Tested, Args...>: std::true_type { };
struct S {
bool b;
//bool c; // causes error
};
int main() {
static_assert(is_list_constructable<S(bool)>::value, "!");
}
[live demo]
I am trying to conditionally enable a constructor template. With a fully C++11-compliant compiler, I know how to do this using an extra default template argument. However, I need to support VS2012, which has std::enable_if but does not support defaulted function template arguments.
With C++11, I would write the following:
template<typename T>
struct Class
{
template<typename O,
typename = typename std::enable_if<std::is_convertible<O*, T*>::value>::type>
Class(O*) {}
};
I tried the following, but it gives an error C4336 and various follow-up errors:
template<typename T>
struct Class
{
template <typename O>
Class(O*, typename std::enable_if<std::is_convertible<O*, T*>::value>::type *= nullptr)
{
}
};
Is there any way to make this work with VS2012?
Addition:
The usage of the class would be as follows:
struct X { };
struct X2 : X { };
struct Y { };
struct Client
{
Client(Class<X> x) {}
Client(Class<Y> y) {}
};
void test() {
X2* x2;
Client client(x2); // error C2668: ambiguous call to overloaded function
// (without std::enable_if)
}
You are so close to the solution !
template<typename T>
struct Class
{
template <typename O>
Class(O*, typename std::enable_if<std::is_convertible<O*, T*>::value>::type * = nullptr)
{
}
};
Did you spot the difference ? *= inside the parameter list was parsed as the multiplication/assignment operator, not as a pointer type followed by a default argument. Hence, syntax errors.
This is because the C++ parser is specified to consume as many characters as possible when it forms a token (the so-called Maximum Munch Rule). Adding a space splits it into two separate tokens, as intended.
I am afraid, you'd have to use a helper construct function (I didn't find a way around it). But something like this should work:
#include <type_traits>
template<typename T>
struct Class
{
template<typename O>
Class(O* o) { construct(o, std::integral_constant<bool, std::is_convertible<T*, O*>::value>()); }
template<class O>
void construct(O*, std::true_type ) { /* convertible */ }
template<class O>
void construct(O*, ... ) { /* not convertible */ }
};
struct X { };
struct Y : public X { };
void check() {
X x;
int i;
Class<Y> cl(&x);
Class<Y> cl1(&i);
}
Since C++11, you can also use delegating constructors to do that:
template<typename T>
class Class {
template<typename O>
Class(O *o, std::true_type) {}
template<typename O>
Class(O *o, std::false_type) {}
public:
template<typename O>
Class(O *o): Class(o, typename std::is_convertible<O*, T*>::type) {}
};
The basic idea is the one of tag dispatching and maybe it works fine also with VS2012.
See here for further details.
I tried to make a traits to find if a method is virtual: (https://ideone.com/9pfaCZ)
// Several structs which should fail depending if T::f is virtual or not.
template <typename T> struct Dvf : T { void f() final; };
template <typename T> struct Dvo : T { void f() override; };
template <typename T> struct Dnv : T { void f() = delete; };
template <typename U>
class has_virtual_f
{
private:
template <std::size_t N> struct helper {};
template <typename T>
static std::uint8_t check(helper<sizeof(Dvf<T>)>*);
template<typename T> static std::uint16_t check(...);
public:
static
constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t);
};
Test cases:
struct V { virtual void f(); };
struct NV { void f(); };
struct E { };
struct F { virtual void f() final; }; // Bonus (unspecified expected output)
static_assert( has_virtual_f< V>::value, "");
static_assert(!has_virtual_f<NV>::value, "");
static_assert(!has_virtual_f< E>::value, "");
But I got error: 'void Dvf<T>::f() [with T = NV]' marked final, but is not virtual.
If I don't use sizeof and directly Dvf<T>* in check, I don't have compilation error, but check is not discarded for "bad" type in SFINAE :( .
What is the proper way to detect if a method is virtual ?
The code isn't perfect but it basically passes the tests (at least in all clangs available on wandbox and gcc since 7.):
#include <type_traits>
template <class T>
using void_t = void;
template <class T, T v1, T v2, class = std::integral_constant<bool, true>>
struct can_be_compaired: std::false_type { };
template <class T, T v1, T v2>
struct can_be_compaired<T, v1, v2, std::integral_constant<bool, v1 == v2>>: std::true_type { };
template <class T, class = void>
struct has_virtual_f: std::false_type { };
template <class T>
struct has_virtual_f<T, void_t<decltype(&T::f)>>{
constexpr static auto value = !can_be_compaired<decltype(&T::f), &T::f, &T::f>::value;
};
struct V { virtual void f() { } };
struct NV { void f() { } };
struct E { };
struct F { virtual void f() final{ } }; // Bonus (unspecified expected output)
int main() {
static_assert( has_virtual_f< V>::value, "");
static_assert(!has_virtual_f<NV>::value, "");
static_assert(!has_virtual_f< E>::value, "");
static_assert( has_virtual_f< F>::value, "");
}
[live demo]
The relevant standard parts that theoretically let the trait fly: [expr.eq]/4.3, [expr.const]/4.23
There is probably no way to determine if a specific method is virtual. I say this because the Boost project researched traits for years and never produced such a traits test.
However, in C++11, or using the Boost library, you can use the is_polymorphic<> template to test a type to see if the type has virtual functions. See std::is_polymorphic<> or boost::is_polymorphic<> for reference.
I tried to make a traits to find if a method is virtual: (https://ideone.com/9pfaCZ)
// Several structs which should fail depending if T::f is virtual or not.
template <typename T> struct Dvf : T { void f() final; };
template <typename T> struct Dvo : T { void f() override; };
template <typename T> struct Dnv : T { void f() = delete; };
template <typename U>
class has_virtual_f
{
private:
template <std::size_t N> struct helper {};
template <typename T>
static std::uint8_t check(helper<sizeof(Dvf<T>)>*);
template<typename T> static std::uint16_t check(...);
public:
static
constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t);
};
Test cases:
struct V { virtual void f(); };
struct NV { void f(); };
struct E { };
struct F { virtual void f() final; }; // Bonus (unspecified expected output)
static_assert( has_virtual_f< V>::value, "");
static_assert(!has_virtual_f<NV>::value, "");
static_assert(!has_virtual_f< E>::value, "");
But I got error: 'void Dvf<T>::f() [with T = NV]' marked final, but is not virtual.
If I don't use sizeof and directly Dvf<T>* in check, I don't have compilation error, but check is not discarded for "bad" type in SFINAE :( .
What is the proper way to detect if a method is virtual ?
The code isn't perfect but it basically passes the tests (at least in all clangs available on wandbox and gcc since 7.):
#include <type_traits>
template <class T>
using void_t = void;
template <class T, T v1, T v2, class = std::integral_constant<bool, true>>
struct can_be_compaired: std::false_type { };
template <class T, T v1, T v2>
struct can_be_compaired<T, v1, v2, std::integral_constant<bool, v1 == v2>>: std::true_type { };
template <class T, class = void>
struct has_virtual_f: std::false_type { };
template <class T>
struct has_virtual_f<T, void_t<decltype(&T::f)>>{
constexpr static auto value = !can_be_compaired<decltype(&T::f), &T::f, &T::f>::value;
};
struct V { virtual void f() { } };
struct NV { void f() { } };
struct E { };
struct F { virtual void f() final{ } }; // Bonus (unspecified expected output)
int main() {
static_assert( has_virtual_f< V>::value, "");
static_assert(!has_virtual_f<NV>::value, "");
static_assert(!has_virtual_f< E>::value, "");
static_assert( has_virtual_f< F>::value, "");
}
[live demo]
The relevant standard parts that theoretically let the trait fly: [expr.eq]/4.3, [expr.const]/4.23
There is probably no way to determine if a specific method is virtual. I say this because the Boost project researched traits for years and never produced such a traits test.
However, in C++11, or using the Boost library, you can use the is_polymorphic<> template to test a type to see if the type has virtual functions. See std::is_polymorphic<> or boost::is_polymorphic<> for reference.
I have been having some inexplicable SFINAE problems in a program I'm writing, so I boiled it down to a freestanding example program:
#include <type_traits>
struct Base { };
struct Derived : public Base { };
template<typename T>
struct From { };
template<typename T>
struct To {
template<typename U>
To(const From<U>& other) {
static_assert(std::is_convertible<U*, T*>::value, "error");
}
};
int main() {
From<Derived> a;
To<Base> b = a;
}
This program compiles without error or warning. However, this:
#include <type_traits>
struct Base { };
struct Derived : public Base { };
template<typename T>
struct From { };
template<typename T>
struct To {
template<typename U>
To(const From<typename std::enable_if<true, U>::type>& other) {
// this ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
static_assert(std::is_convertible<U*, T*>::value, "error");
}
};
int main() {
From<Derived> a;
To<Base> b = a;
}
Gives the following error:
test.cpp: In function int main():
test.cpp:22:18: error: conversion from From<Base> to non-scalar type To<Derived> requested
Which is because the substitution fails I presume, and the constructor isn't seen.
Am I doing SFINAE wrong, or is this a compiler bug? I am using rubenvb's GCC 4.7.1 on Windows (with std=c++11 if it makes a difference).
I'd use a default template argument, that way the argument can be deduced:
#include <type_traits>
struct Base { };
struct Derived : public Base { };
template<typename T>
struct From { };
template<typename T>
struct To {
template<typename U, class = typename std::enable_if<true, U>::type>
To(const From<U>& other) {
// this ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
static_assert(std::is_convertible<U*, T*>::value, "error");
}
};
int main() {
From<Base> a;
To<Derived> b = a;
}
Note that that causes the static_assert to fail since you are using std::is_convertible the other way around. It should be:
static_assert(std::is_convertible<T*, U*>::value, "error");
In your example, the template type U can't be deduced. In my code, it can be deduced, since it is being used as a template argument for the other argument in the constructor. In your code, the compiler sees a std::enable_if<true, U>::type and can't deduce what that U type is. The fact that the result of that enable_if is being used as a template argument for From doesn't help at all, since U needs to be deduced before that.