I am studying SFINAE and c++ in general. I'm having a strange behaviour with My SFINAE macros (called here "annotations"):
<lang/Annotations.h>
#pragma once
#define ENABLE_IF(y) typename std::enable_if<y,std::nullptr_t>::type
#define IS_REFERENCE(x) std::is_reference<x>::value
#define IS_CONSTRUCTIBLE(...) std::is_constructible<__VA_ARGS__>::value
I made a custom "MY_OBJECT
" class, which provides the following constructor:
MY_OBJECT(const char* stringDataPtr) noexcept;
The objective here is the following:
By using a variadic template function, each template argument's type must be checked: if it can be passed to
String
constructor (std::is_constructible
) then a message "is constructible" must be printed, otherwise "is not constructible" must be printed.
The problem
Even by passing int
values, my SFINAE method does not get "SFINAE'd" and I always get "Is constructible" message.
<util/SFINAETools.h
namespace SFINAETools {
enum class SFINAEResult {
IS_CONSTRUCTIBLE,
IS_NOT_CONSTRUCTIBLE,
IS_REFERENCE,
IS_NOT_REFERENCE
};
std::ostream& operator<<(std::ostream& out, const SFINAEResult& value) {
static std::unordered_map<SFINAEResult, System::MyString> strings {
{SFINAEResult::IS_CONSTRUCTIBLE, "IS_CONSTRUCTIBLE"},
{SFINAEResult::IS_NOT_CONSTRUCTIBLE, "IS_NOT_CONSTRUCTIBLE"},
{SFINAEResult::IS_REFERENCE, "IS_REFERENCE"},
{SFINAEResult::IS_NOT_REFERENCE, "IS_NOT_REFERENCE"}
};
return out << strings[value];
}
class SFINAECallbackHandler : public Object {
public:
virtual void onSFINAEResult(const SFINAEResult& result) const = 0;
};
template <typename... ARGS, ENABLE_IF(IS_CONSTRUCTIBLE(ARGS...))>
void executeIfConstructible(const SFINAECallbackHandler& callBackHandler) {
callBackHandler.onSFINAEResult(SFINAEResult::IS_CONSTRUCTIBLE);
}
template <typename... ARGS, ENABLE_IF(!IS_CONSTRUCTIBLE(ARGS...))>
void executeIfConstructible(const SFINAECallbackHandler& callBackHandler) {
callBackHandler.onSFINAEResult(SFINAEResult::IS_NOT_CONSTRUCTIBLE);
}
template <typename X, ENABLE_IF(IS_REFERENCE(X))>
void executeIfIsReference(const SFINAECallbackHandler& callBackHandler) {
callBackHandler.onSFINAEResult(SFINAEResult::IS_REFERENCE);
}
template <typename X, ENABLE_IF(!IS_REFERENCE(X))>
void executeIfIsReference(const SFINAECallbackHandler& callBackHandler) {
callBackHandler.onSFINAEResult(SFINAEResult::IS_NOT_REFERENCE);
}
};
MAIN.cpp
#include <lang/CppApplication.h>
#include <util/SFINAETools.h>
class MyCallbackHandler :public SFINAETools::SFINAECallbackHandler {
public:
virtual void onSFINAEResult(const SFINAETools::SFINAEResult& result) const override {
std::cout << result << std::endl;
}
};
class MY_OBJECT : public Object {
public:
MY_OBJECT(const char* strDataPtr) {
}
};
class Main : public CppApplication {
public:
virtual int main(const std::vector<String>& arguments) override {
createString(1, "2");
return 0;
}
template <typename Arg1>
void createString(Arg1&& arg1) {
SFINAETools::executeIfConstructible<MY_OBJECT, Arg1>(MyCallbackHandler());
}
template <typename Arg1, typename ...Args>
void createString(Arg1&& arg1, Args&&... args) {
createString(arg1);
createString(args...);
}
template <typename ...Args>
void createString(Args&&... args) {
std::list<MY_OBJECT> list;
createString(list, args...);
}
};
I don't know if it is the problem (the only problem) but this macro
#define ENABLE_IF(y) typename std::enable_if<y>::type
becomes void
when y
is true.
So when y
is true
template <typename... Types, ENABLE_IF(!IS_CONSTRUCTIBLE(Types...)) = 0>
becomes
template <typename... Types, void = 0>
That can't work. 0
isn't a valid value for void
. There are no valid values for void
.
And
template <typename X, ENABLE_IF(IS_REFERENCE(X))>
becomes
template <typename X, void>
That is even worse.
I suppose you can define ENABLE_IF
to return (in this case) an int
// ...........................................VVVVV
#define ENABLE_IF(y) typename std::enable_if<y, int>::type
remembering the = 0
after every ENABLE_IF
Another problem: now you have
template <typename X, typename Y>
static bool executeIfConstructible(std::function<void()> predicate) {
predicate();
return true;
}
template <typename X, typename Y , ENABLE_IF(!IS_CONSTRUCTIBLE(X,Y))>
static bool executeIfConstructible(std::function<void()> predicate) {
return false;
}
So you have two version of executeIfContructible()
: the first one always enabled, the second one enabled only when !IS_CONSTRUCTIBLE(X,Y)
is true.
You have to disable the first one when !IS_CONSTRUCTIBLE(X,Y)
is false (when IS_CONSTRUCTIBLE(X,Y)
) or you'll have an ambiguous call when the second one is enabled.
template <typename X, typename Y , ENABLE_IF(IS_CONSTRUCTIBLE(X,Y))>
static bool executeIfConstructible(std::function<void()> predicate) {
predicate();
return true;
}
template <typename X, typename Y , ENABLE_IF(!IS_CONSTRUCTIBLE(X,Y))>
static bool executeIfConstructible(std::function<void()> predicate) {
return false;
}
Unrequested suggestion: C-style macros are distilled evil. Avoid C-style macros when you can.
For example, instead a macro for ENABLE_IF
, define a using
template <bool B>
using EnableIf = typename std::enable_if<B, int>::type;
For IS_REFERENCE
and IS_CONSTRUCTIBLE
—if you are sure you need them—you can define (starting from C++14) a couple of constexpr
template variables
template <bool B>
constexpr bool IsReference = std::is_reference<B>::value;
template <typename ... Ts>
constexpr bool IsConstructible = std::is_constructible<Ts...>::value;