Created
February 22, 2016 10:27
-
-
Save thenickdude/5d9995f409418a4a66ac to your computer and use it in GitHub Desktop.
C++ SFINAE: Call a 2 argument constructor if it exists for a templated type, otherwise call the no-arg constructor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <type_traits> | |
#include <cstdint> | |
#include <cstddef> | |
// Or we can use enable_if from Boost: | |
template <bool, typename T = void> | |
struct enable_if { | |
}; | |
template <typename T> | |
struct enable_if<true, T> { | |
typedef T type; | |
}; | |
template <typename T> | |
struct CtorTakes2Ints { | |
/* If T provides a constructor matching this signature, this is the declaration of SFINAE that will succeed, | |
* of size 4 bytes | |
*/ | |
template<typename U> | |
static int32_t SFINAE(decltype(U(int(), int()))*); | |
/* Otherwise the ellipsis will accept just about anything (and has minimum priority) so in the fallback case | |
* we'll use this definition and SFINAE will be 1 byte big | |
*/ | |
template<typename U> | |
static int8_t SFINAE(...); | |
// Check what size SFINAE ended up being, this tells us if the constructor matched the right signature or not | |
static const bool value = sizeof(SFINAE<T>(nullptr)) == sizeof(int32_t); | |
}; | |
class CandidateA { | |
public: | |
CandidateA(int a, int b) { | |
std::cout << "CandidateA: Two arg constructor called: " << a << ", " << b << std::endl; | |
} | |
}; | |
class CandidateB { | |
public: | |
CandidateB() { | |
std::cout << "CandidateB: No-arg constructor called" << std::endl; | |
} | |
}; | |
template <typename T> | |
class Collection { | |
private: | |
/** | |
* Construct an element which has a constructor with two int arguments. | |
*/ | |
template <class U> | |
/* Here we have a dummy argument which defaults to a null pointer of type U* if there is a 2-int constructor. | |
* | |
* Otherwise the resolution of enable_if will fail. The compiler will quietly discard this method | |
* during overload resolution and call the no-arg constructor version instead. | |
*/ | |
U constructCandidate(typename enable_if<CtorTakes2Ints<U>::value, U>::type* = 0) { | |
return U(1, 2); | |
} | |
/** | |
* Fallback for element types without two-int signatures. | |
*/ | |
template <class U> | |
U constructCandidate(typename enable_if<!CtorTakes2Ints<U>::value, U>::type* = 0) { | |
return U(); | |
} | |
public: | |
void addElement() { | |
// Calls one of the two specialisations above depending on T's constructor | |
T elem = constructCandidate<T>(); | |
// ... add it to the container here | |
} | |
}; | |
int main(void) { | |
Collection<CandidateA> collectionA; | |
collectionA.addElement(); | |
Collection<CandidateB> collectionB; | |
collectionB.addElement(); | |
} | |
// Compiled with: g++ -std=c++11 -o test test.cpp | |
// Prints: | |
// CandidateA: Two arg constructor called: 1, 2 | |
// CandidateB: No-arg constructor called |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment