Skip to content

Instantly share code, notes, and snippets.

@thenickdude
Created February 22, 2016 10:27
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thenickdude/5d9995f409418a4a66ac to your computer and use it in GitHub Desktop.
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
#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