I want to check whether a function can be evaluated during compilation. I found this, but I don't understand the concept completely. I have a few doubts:
What is the role of the following line in the code?
template<int Value = Trait::f()>
Every time when I need to check whether the function is compile-time evaluable, Do I need to make it a member function of some struct?
PS
I am copying the code in the link, just for convenience.
template<typename Trait>
struct test
{
template<int Value = Trait::f()>
static std::true_type do_call(int){ return std::true_type(); }
static std::false_type do_call(...){ return std::false_type(); }
static bool call(){ return do_call(0); }
};
struct trait
{
static int f(){ return 15; }
};
struct ctrait
{
static constexpr int f(){ return 20; }
};
int main()
{
std::cout << "regular: " << test<trait>::call() << std::endl;
std::cout << "constexpr: " << test<ctrait>::call() << std::endl;
}
Here is just a quick example of what you can get with std::void_t to tackle your point 2 that can be generic in some way...
#include <iostream>
#include <type_traits>
int f() {
return 666;
}
constexpr int cf(int, double) {
return 999;
}
template <auto F>
struct indirection {
};
template<typename F, class = std::void_t<> >
struct is_constexpr : std::false_type { };
template<typename F, typename... Args>
struct is_constexpr<F(Args...),
std::void_t<indirection<F(Args{}...)>>
> : std::true_type { };
int main()
{
std::cout << is_constexpr<decltype(f)>::value << std::endl;
std::cout << is_constexpr<decltype(cf)>::value << std::endl;
};
Demo here
Related
In the example below I can effectively strip the const, volatile and reference qualifiers and use the single specialization for shared pointers. This is solved by the adding one more level of abstraction. How could I solve this without doing so? I could I just use the specialisations and match on shared_pointer, shared_pointer const etc?
#include <iostream>
#include <type_traits>
namespace detail {
template<typename T>
struct display;
template<typename T>
struct display<std::shared_ptr<T>> {
static void apply() {
std::cout << __FUNCTION__ << std::endl;
}
};
}
template<typename T>
void display() {
detail::display<std::remove_cvref_t<T>>::apply();
}
int main() {
std::shared_ptr<int> t;
display<decltype(t)>();
return 0;
}
So I have come up with a solution which I like much better which I thought I would share.
template<typename T>
struct is_shared_pointer : std::false_type { };
template<template<typename > typename T, typename U>
struct is_shared_pointer<T<U>> : std::is_same<std::decay_t<T<U>>, std::shared_ptr<U>> {};
template<typename T, typename Enable = void>
struct display;
template<typename T>
struct display<T, std::enable_if_t<is_shared_pointer<T>::value>> {
static void apply() {
std::cout << "shared ptr: " << __FUNCTION__ << std::endl;
}
};
template<typename T>
struct display<T, std::enable_if_t<std::is_integral_v<T>>> {
static void apply() {
std::cout << "integral :" << __FUNCTION__ << std::endl;
}
};
template<typename T>
struct display<T, std::enable_if_t<std::is_void_v<T>>> {
static void apply() {
std::cout << "void: " << __FUNCTION__ << std::endl;
}
};
template<typename T>
struct display<T, std::enable_if_t<std::is_floating_point_v<T>>> {
static void apply() {
std::cout << "floating: " << __FUNCTION__ << std::endl;
}
};
int main() {
std::shared_ptr<int> t;
display<decltype(t)>();
return 0;
}
That being said, I am open to suggestions, ideas and techniques.
I wrote the following code, here as msg_hdr() function has 2 overloads, which makes decltype unusable.
Instead of decltype, I have also tried invoke_result_t as well as result_of_t, but nothing seems to work.
What changes should I make in the code to make it work.
struct Header
{
int i;
int j;
};
struct Data
{
Header& msg_hdr()
{
return this->_msg_hdr;
}
const Header& msg_hdr() const
{
return this->_msg_hdr;
}
private:
Header _msg_hdr;
int k;
};
template<typename, typename = void>
constexpr bool has_msg_hdr = false;
template<typename St>
constexpr bool has_msg_hdr<St, std::enable_if_t<std::is_same_v<decltype(St::msg_hdr), Header>>> = true;
template<typename St>
constexpr void check(St s)
{
if constexpr(has_msg_hdr<std::remove_const_t<std::remove_reference_t<St>>>)
{
std::cout << "has msg_header \n";
}
else
{
std::cout << "no msg_header \n";
}
}
int main(int argc, char** argv)
{
Data d;
check(d);
return 0;
}
We can use this trick to detect particular overload function.
template <typename T, typename... Args>
class has_msg_hdr
{
template <typename C,
typename = decltype( std::declval<C>().msg_hdr(std::declval<Args>()...) )>
static std::true_type test(int);
template <typename C>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
Test code.
std::cout << std::boolalpha << has_msg_hdr<Data>::value << " "
<< has_msg_hdr<Data,int>::value << std::endl;
Is there a way to establish at compile time if a certain function template was specialized?
For example, assume the following function template:
template<size_t N>
void foo();
I want to test if foo<42> was specialized. Note that the declaration above doesn't contain any default implementation.
I tried SFINAE but couldn't find a condition on the function that the compiler cannot deduce from its declaration.
Is there a way to establish in compile time if a certain template function was specialized?
With a function... I don't think so.
But if you create a functor, you can add a static const member (is_specialized, in the following example) that can give you this information
#include <iostream>
template <std::size_t N>
struct foo
{
static constexpr bool is_specialized { false };
void operator() () const
{ std::cout << "- generic (" << N << ") foo struct" << std::endl; }
};
template <>
struct foo<42U>
{
static constexpr bool is_specialized { true };
void operator() () const
{ std::cout << "- specialized (42) foo struct" << std::endl; }
};
int main()
{
foo<17U>()(); // print - generic (17) foo struct
foo<42U>()(); // print - specialized (42) foo struct
std::cout << foo<17U>::is_specialized << std::endl; // print 0
std::cout << foo<42U>::is_specialized << std::endl; // print 1
}
--- EDIT ---
Following the suggestion from Quentin (thanks again!) I've developed another functor-based solution that use something, to detect if the functor is generic or specialize, that is added only in the generic functor. In this case, a type instead a bool constant.
template <std::size_t N>
struct foo
{
// im_not_specialized is added only in the generic version!
using im_not_specialized = void;
void operator () () const
{ std::cout << "- generic (" << N << ") foo struct" << std::endl; }
};
template <>
struct foo<42U>
{
void operator () () const
{ std::cout << "- specialized (42) foo struct" << std::endl; }
};
This type can be used via SFINAE and I propose an example based on a constexpr isSpecialized() template function (with an helper function)
template <typename F>
constexpr bool isSpecializedHelper
(int, typename F::im_not_specialized const * = nullptr)
{ return false; }
template <typename F>
constexpr bool isSpecializedHelper (long)
{ return true; }
template <typename F>
constexpr bool isSpecialized ()
{ return isSpecializedHelper<F>(0); }
This require a little more work but isSpecialized() can be reused with different functors (im_not_specialized type based)
The following is a full working example
#include <iostream>
template <std::size_t N>
struct foo
{
// im_not_specialized is added only in the generic version!
using im_not_specialized = void;
void operator () () const
{ std::cout << "- generic (" << N << ") foo struct" << std::endl; }
};
template <>
struct foo<42U>
{
void operator () () const
{ std::cout << "- specialized (42) foo struct" << std::endl; }
};
template <typename F>
constexpr bool isSpecializedHelper
(int, typename F::im_not_specialized const * = nullptr)
{ return false; }
template <typename F>
constexpr bool isSpecializedHelper (long)
{ return true; }
template <typename F>
constexpr bool isSpecialized ()
{ return isSpecializedHelper<F>(0); }
int main()
{
foo<17U>()(); // print - generic (17) foo struct
foo<42U>()(); // print - specialized (42) foo struct
constexpr auto isSp17 = isSpecialized<foo<17U>>();
constexpr auto isSp42 = isSpecialized<foo<42U>>();
std::cout << isSp17 << std::endl; // print 0
std::cout << isSp42 << std::endl; // print 1
}
If you mark the base function as deleted (= delete), you can detect if it has been specialized using SFINAE (assuming the specialization itself is not deleted)
An expression like decltype(foo<N>()) will result in a substitution failure if foo<N> is marked as deleted. If you provide a specialization that is not deleted on the other hand the expression will not result in an error.
Using this you can create a simple trait class to check if foo has been specialized for a specific set of template parameters:
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo<N>(), void())> : std::true_type {};
1. Basic examples
C++11: godbolt
#include <type_traits>
template<std::size_t N>
void foo() = delete;
template<>
void foo<1>() { }
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo<N>(), void())> : std::true_type {};
int main()
{
static_assert(!is_foo_specialized<0>::value, ""); // foo<0> is not specialized
static_assert(is_foo_specialized<1>::value, ""); // foo<1> IS specialized
}
With C++20 you could also use a concept for this, e.g.: godbolt
#include <type_traits>
template<std::size_t N>
void foo() = delete;
template<>
void foo<1>() { }
template<std::size_t N>
concept is_foo_specialized = requires { foo<N>(); };
int main()
{
static_assert(!is_foo_specialized<0>); // foo<0> is not specialized
static_assert(is_foo_specialized<1>); // foo<1> IS specialized
}
2. Providing a default implementation
Due to the function being = delete'd it can't have a default implementation.
If you do require a default implementation for the function, you could use 2 functions instead:
one that is = delete'd (so SFINAE can detect it)
and another one that implements the default behaviour and forwards to the other if a specialization exists
C++11: godbolt
#include <type_traits>
#include <iostream>
template<std::size_t N>
void foo_specialized() = delete;
template<>
void foo_specialized<1>() { std::cout << "CUSTOMIZED!" << std::endl; }
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo_specialized<N>(), void())> : std::true_type {};
template<std::size_t N>
typename std::enable_if<!is_foo_specialized<N>::value>::type foo() {
std::cout << "DEFAULT!" << std::endl;
}
template<std::size_t N>
typename std::enable_if<is_foo_specialized<N>::value>::type foo() {
foo_specialized<N>();
}
int main()
{
foo<0>(); // -> DEFAULT!
foo<1>(); // -> CUSTOMIZED!
}
Or with C++20: godbolt
#include <type_traits>
#include <iostream>
template<std::size_t N>
void foo_specialize() = delete;
template<>
void foo_specialize<1>() { std::cout << "CUSTOMIZED!" << std::endl; }
template<std::size_t N>
concept is_foo_specialized = requires { foo_specialize<N>(); };
template<std::size_t N> requires (!is_foo_specialized<N>)
void foo() {
std::cout << "DEFAULT!" << std::endl;
}
template<std::size_t N> requires (is_foo_specialized<N>)
void foo() {
foo_specialize<N>();
}
int main()
{
foo<0>(); // -> DEFAULT!
foo<1>(); // -> CUSTOMIZED!
}
3. Compile-time shenanigans
This can of course also be used to iterate the specializations (within a certain limit) - or like you asked in the comments to find the nearest specialization of the function.
nearest_foo_specialized in this example will iterate over a range of values for N and check if a specialization of foo exists for this value.
N is the value where we want to start the search
SearchRange determines how many specializations will be checked (both up and down) from the provided N value (in this example we check for N's +/- 10)
CurrentDistance keeps track how far we've already searched from our starting N value, so we don't exceed the specified SearchRange
The last template parameter is used for SFINAE
e.g.:
nearest_foo_specialized<100, 10> would check for specializations of foo between N = 90 and N = 110, returning the one that is closer to 100 (prefering lower N values in case of a draw)
Example C++11: godbolt
#include <type_traits>
#include <iostream>
#include <utility>
template<std::size_t N>
void foo() = delete;
template<>
void foo<5>() { std::cout << 5 << std::endl; }
template<>
void foo<10>() { std::cout << 10 << std::endl; }
template<>
void foo<15>() { std::cout << 15 << std::endl; }
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo<N>(), void())> : std::true_type {};
template<std::size_t N, std::size_t SearchRange = 10, std::size_t CurrentDistance = 0, class = void>
struct nearest_foo_specialized {
static const std::size_t index = 0; // an index for which foo<> is specialized, if value is true.
static const std::size_t distance = CurrentDistance; // distance from original N
static const bool value = false; // have we found a specialization yet?
};
// Found a match -> Report Success
template<std::size_t N, std::size_t SearchRange, std::size_t CurrentDistance>
struct nearest_foo_specialized<N, SearchRange, CurrentDistance, typename std::enable_if< CurrentDistance <= SearchRange && is_foo_specialized<N>::value >::type> {
static const std::size_t index = N;
static const std::size_t distance = CurrentDistance;
static const bool value = true;
};
// No match found -> recurse until SearchRange limit
template<std::size_t N, std::size_t SearchRange, std::size_t CurrentDistance>
struct nearest_foo_specialized<N, SearchRange, CurrentDistance, typename std::enable_if< CurrentDistance < SearchRange && !is_foo_specialized<N>::value >::type> {
typedef nearest_foo_specialized<N - 1, SearchRange, CurrentDistance + 1> down;
typedef nearest_foo_specialized<N + 1, SearchRange, CurrentDistance + 1> up;
static const std::size_t distance = down::distance < up::distance ? down::distance : up::distance;
static const std::size_t index = down::distance == distance && down::value ? down::index : up::index;
static const std::size_t value = down::distance == distance && down::value ? down::value : up::value;
};
// calls the nearest foo() specialization (up to 10 away from the specified N)
template<std::size_t N>
typename std::enable_if<nearest_foo_specialized<N>::value>::type call_nearest_foo() {
foo<nearest_foo_specialized<N>::index>();
}
template<std::size_t N>
typename std::enable_if<!nearest_foo_specialized<N>::value>::type call_nearest_foo() {
static_assert(N!=N, "No nearest foo() specialization found!");
}
int main() {
call_nearest_foo<7>(); // calls foo<5>()
call_nearest_foo<8>(); // calls foo<10>()
call_nearest_foo<11>(); // calls foo<10>()
call_nearest_foo<15>(); // calls foo<15>()
call_nearest_foo<25>(); // calls foo<15>()
// call_nearest_foo<26>(); // error: No nearest foo() (only searching up to 10 up / down)
}
I need to define a class, foo, with a static member variable template, foo::static_variable_template<T>. This member should only exist when T fulfills certain requirements. For example, when the constexpr static function T::constexpr_static_function() exists. Otherwise, foo::static_variable_template<T> should not exist. Moreover, I would like to be able to test for the existence of foo::static_variable_template<T> at compile-time via SFINAE.
Here is an approximation of what I would like to do:
#include <iostream>
struct foo
{
template<class T>
static constexpr int static_variable_template =
T::constexpr_static_function();
// XXX this works but requires a second defaulted template parameter
// template<class T, int = T::constexpr_static_function()>
// static constexpr int static_variable_template =
// T::constexpr_static_function();
};
struct has_constexpr_static_function
{
static constexpr int constexpr_static_function() { return 42; }
};
struct hasnt_constexpr_static_function
{
};
template<class T, class U,
int = T::template static_variable_template<U>>
void test_for_static_variable_template(int)
{
std::cout << "yes it has\n";
}
template<class T, class U>
void test_for_static_variable_template(...)
{
std::cout << "no it hasn't\n";
}
int main()
{
test_for_static_variable_template<foo, has_constexpr_static_function>(0);
test_for_static_variable_template<foo, hasnt_constexpr_static_function>(0);
}
This approximation nearly works, but only if foo::static_variable_template has a second, defaulted template parameter. Because this second parameter is an implementation detail, I'd like to hide it from the public interface of foo::static_variable_template.
Is this possible in C++17?
I am not sure if your intent is to initialise foo::static_variable_template with 0 if T::constexpr_static_function() is missing or you want to disable it entirely. In case of the former, this might be useful. For example, this (clunky) solution works (requires C++17 for if constexpr; note that your variable is now a function):
#include <iostream>
template <typename T>
class has_func
{
typedef char does;
typedef long doesnt;
template <typename C> static does test( decltype(&C::constexpr_static_function) );
template <typename C> static doesnt test(...);
public:
static constexpr bool value()
{
return sizeof(test<T>(0)) == sizeof(char);
}
};
struct foo
{
template<class T>
static constexpr int static_variable_template()
{
if constexpr (has_func<T>::value())
{
return T::constexpr_static_function();
}
return 0;
}
// XXX this works but requires a second defaulted template parameter
// template<class T, int = T::constexpr_static_function()>
// static constexpr int static_variable_template =
// T::constexpr_static_function();
};
struct has_constexpr_static_function
{
static constexpr int constexpr_static_function() { return 42; }
};
struct hasnt_constexpr_static_function
{
};
template<class T, class U>
void test_for_static_variable_template(...)
{
if constexpr (has_func<U>::value())
{
std::cout << "yes it has\n";
}
else
{
std::cout << "no it hasn't\n";
}
}
int main()
{
std::cout << foo::static_variable_template<has_constexpr_static_function>() << "\n";
std::cout << foo::static_variable_template<hasnt_constexpr_static_function>() << "\n";
/// Original test
test_for_static_variable_template<foo, has_constexpr_static_function>(0);
test_for_static_variable_template<foo, hasnt_constexpr_static_function>(0);
}
Prints
42
0
yes it has
no it hasn't
Tested with clang 5.0.1.
In case you want to disable foo::static_variable_template entirely, you might need to use std::enable_if:
#include <iostream>
template <typename T>
class has_func
{
typedef char does;
typedef long doesnt;
template <typename C> static does test( decltype(&C::constexpr_static_function) );
template <typename C> static doesnt test(...);
public:
static constexpr bool value()
{
return sizeof(test<T>(0)) == sizeof(char);
}
};
struct foo
{
template<class T, typename std::enable_if<has_func<T>::value()>::type ...>
static constexpr int static_variable_template()
{
if constexpr (has_func<T>::value())
{
return T::constexpr_static_function();
}
return 0;
}
// XXX this works but requires a second defaulted template parameter
// template<class T, int = T::constexpr_static_function()>
// static constexpr int static_variable_template =
// T::constexpr_static_function();
};
struct has_constexpr_static_function
{
static constexpr int constexpr_static_function() { return 42; }
};
struct hasnt_constexpr_static_function
{
};
template<class T, class U>
void test_for_static_variable_template(...)
{
if constexpr (has_func<U>::value())
{
std::cout << "yes it has\n";
}
else
{
std::cout << "no it hasn't\n";
}
}
int main()
{
std::cout << foo::static_variable_template<has_constexpr_static_function>() << "\n";
// We can't print this because it doesn't exist.
// std::cout << foo::static_variable_template<hasnt_constexpr_static_function>() << "\n";
/// Original test
test_for_static_variable_template<foo, has_constexpr_static_function>(0);
test_for_static_variable_template<foo, hasnt_constexpr_static_function>(0);
}
In this line of thought, I am not sure if you can disable a static template variable with std::enable_if. To quote the great Riemann, "I have for the time being, after some fleeting vain attempts, provisionally put aside the search for this..."
Is there a way to establish at compile time if a certain function template was specialized?
For example, assume the following function template:
template<size_t N>
void foo();
I want to test if foo<42> was specialized. Note that the declaration above doesn't contain any default implementation.
I tried SFINAE but couldn't find a condition on the function that the compiler cannot deduce from its declaration.
Is there a way to establish in compile time if a certain template function was specialized?
With a function... I don't think so.
But if you create a functor, you can add a static const member (is_specialized, in the following example) that can give you this information
#include <iostream>
template <std::size_t N>
struct foo
{
static constexpr bool is_specialized { false };
void operator() () const
{ std::cout << "- generic (" << N << ") foo struct" << std::endl; }
};
template <>
struct foo<42U>
{
static constexpr bool is_specialized { true };
void operator() () const
{ std::cout << "- specialized (42) foo struct" << std::endl; }
};
int main()
{
foo<17U>()(); // print - generic (17) foo struct
foo<42U>()(); // print - specialized (42) foo struct
std::cout << foo<17U>::is_specialized << std::endl; // print 0
std::cout << foo<42U>::is_specialized << std::endl; // print 1
}
--- EDIT ---
Following the suggestion from Quentin (thanks again!) I've developed another functor-based solution that use something, to detect if the functor is generic or specialize, that is added only in the generic functor. In this case, a type instead a bool constant.
template <std::size_t N>
struct foo
{
// im_not_specialized is added only in the generic version!
using im_not_specialized = void;
void operator () () const
{ std::cout << "- generic (" << N << ") foo struct" << std::endl; }
};
template <>
struct foo<42U>
{
void operator () () const
{ std::cout << "- specialized (42) foo struct" << std::endl; }
};
This type can be used via SFINAE and I propose an example based on a constexpr isSpecialized() template function (with an helper function)
template <typename F>
constexpr bool isSpecializedHelper
(int, typename F::im_not_specialized const * = nullptr)
{ return false; }
template <typename F>
constexpr bool isSpecializedHelper (long)
{ return true; }
template <typename F>
constexpr bool isSpecialized ()
{ return isSpecializedHelper<F>(0); }
This require a little more work but isSpecialized() can be reused with different functors (im_not_specialized type based)
The following is a full working example
#include <iostream>
template <std::size_t N>
struct foo
{
// im_not_specialized is added only in the generic version!
using im_not_specialized = void;
void operator () () const
{ std::cout << "- generic (" << N << ") foo struct" << std::endl; }
};
template <>
struct foo<42U>
{
void operator () () const
{ std::cout << "- specialized (42) foo struct" << std::endl; }
};
template <typename F>
constexpr bool isSpecializedHelper
(int, typename F::im_not_specialized const * = nullptr)
{ return false; }
template <typename F>
constexpr bool isSpecializedHelper (long)
{ return true; }
template <typename F>
constexpr bool isSpecialized ()
{ return isSpecializedHelper<F>(0); }
int main()
{
foo<17U>()(); // print - generic (17) foo struct
foo<42U>()(); // print - specialized (42) foo struct
constexpr auto isSp17 = isSpecialized<foo<17U>>();
constexpr auto isSp42 = isSpecialized<foo<42U>>();
std::cout << isSp17 << std::endl; // print 0
std::cout << isSp42 << std::endl; // print 1
}
If you mark the base function as deleted (= delete), you can detect if it has been specialized using SFINAE (assuming the specialization itself is not deleted)
An expression like decltype(foo<N>()) will result in a substitution failure if foo<N> is marked as deleted. If you provide a specialization that is not deleted on the other hand the expression will not result in an error.
Using this you can create a simple trait class to check if foo has been specialized for a specific set of template parameters:
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo<N>(), void())> : std::true_type {};
1. Basic examples
C++11: godbolt
#include <type_traits>
template<std::size_t N>
void foo() = delete;
template<>
void foo<1>() { }
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo<N>(), void())> : std::true_type {};
int main()
{
static_assert(!is_foo_specialized<0>::value, ""); // foo<0> is not specialized
static_assert(is_foo_specialized<1>::value, ""); // foo<1> IS specialized
}
With C++20 you could also use a concept for this, e.g.: godbolt
#include <type_traits>
template<std::size_t N>
void foo() = delete;
template<>
void foo<1>() { }
template<std::size_t N>
concept is_foo_specialized = requires { foo<N>(); };
int main()
{
static_assert(!is_foo_specialized<0>); // foo<0> is not specialized
static_assert(is_foo_specialized<1>); // foo<1> IS specialized
}
2. Providing a default implementation
Due to the function being = delete'd it can't have a default implementation.
If you do require a default implementation for the function, you could use 2 functions instead:
one that is = delete'd (so SFINAE can detect it)
and another one that implements the default behaviour and forwards to the other if a specialization exists
C++11: godbolt
#include <type_traits>
#include <iostream>
template<std::size_t N>
void foo_specialized() = delete;
template<>
void foo_specialized<1>() { std::cout << "CUSTOMIZED!" << std::endl; }
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo_specialized<N>(), void())> : std::true_type {};
template<std::size_t N>
typename std::enable_if<!is_foo_specialized<N>::value>::type foo() {
std::cout << "DEFAULT!" << std::endl;
}
template<std::size_t N>
typename std::enable_if<is_foo_specialized<N>::value>::type foo() {
foo_specialized<N>();
}
int main()
{
foo<0>(); // -> DEFAULT!
foo<1>(); // -> CUSTOMIZED!
}
Or with C++20: godbolt
#include <type_traits>
#include <iostream>
template<std::size_t N>
void foo_specialize() = delete;
template<>
void foo_specialize<1>() { std::cout << "CUSTOMIZED!" << std::endl; }
template<std::size_t N>
concept is_foo_specialized = requires { foo_specialize<N>(); };
template<std::size_t N> requires (!is_foo_specialized<N>)
void foo() {
std::cout << "DEFAULT!" << std::endl;
}
template<std::size_t N> requires (is_foo_specialized<N>)
void foo() {
foo_specialize<N>();
}
int main()
{
foo<0>(); // -> DEFAULT!
foo<1>(); // -> CUSTOMIZED!
}
3. Compile-time shenanigans
This can of course also be used to iterate the specializations (within a certain limit) - or like you asked in the comments to find the nearest specialization of the function.
nearest_foo_specialized in this example will iterate over a range of values for N and check if a specialization of foo exists for this value.
N is the value where we want to start the search
SearchRange determines how many specializations will be checked (both up and down) from the provided N value (in this example we check for N's +/- 10)
CurrentDistance keeps track how far we've already searched from our starting N value, so we don't exceed the specified SearchRange
The last template parameter is used for SFINAE
e.g.:
nearest_foo_specialized<100, 10> would check for specializations of foo between N = 90 and N = 110, returning the one that is closer to 100 (prefering lower N values in case of a draw)
Example C++11: godbolt
#include <type_traits>
#include <iostream>
#include <utility>
template<std::size_t N>
void foo() = delete;
template<>
void foo<5>() { std::cout << 5 << std::endl; }
template<>
void foo<10>() { std::cout << 10 << std::endl; }
template<>
void foo<15>() { std::cout << 15 << std::endl; }
template<std::size_t N, class = void>
struct is_foo_specialized : std::false_type {};
template<std::size_t N>
struct is_foo_specialized<N, decltype(foo<N>(), void())> : std::true_type {};
template<std::size_t N, std::size_t SearchRange = 10, std::size_t CurrentDistance = 0, class = void>
struct nearest_foo_specialized {
static const std::size_t index = 0; // an index for which foo<> is specialized, if value is true.
static const std::size_t distance = CurrentDistance; // distance from original N
static const bool value = false; // have we found a specialization yet?
};
// Found a match -> Report Success
template<std::size_t N, std::size_t SearchRange, std::size_t CurrentDistance>
struct nearest_foo_specialized<N, SearchRange, CurrentDistance, typename std::enable_if< CurrentDistance <= SearchRange && is_foo_specialized<N>::value >::type> {
static const std::size_t index = N;
static const std::size_t distance = CurrentDistance;
static const bool value = true;
};
// No match found -> recurse until SearchRange limit
template<std::size_t N, std::size_t SearchRange, std::size_t CurrentDistance>
struct nearest_foo_specialized<N, SearchRange, CurrentDistance, typename std::enable_if< CurrentDistance < SearchRange && !is_foo_specialized<N>::value >::type> {
typedef nearest_foo_specialized<N - 1, SearchRange, CurrentDistance + 1> down;
typedef nearest_foo_specialized<N + 1, SearchRange, CurrentDistance + 1> up;
static const std::size_t distance = down::distance < up::distance ? down::distance : up::distance;
static const std::size_t index = down::distance == distance && down::value ? down::index : up::index;
static const std::size_t value = down::distance == distance && down::value ? down::value : up::value;
};
// calls the nearest foo() specialization (up to 10 away from the specified N)
template<std::size_t N>
typename std::enable_if<nearest_foo_specialized<N>::value>::type call_nearest_foo() {
foo<nearest_foo_specialized<N>::index>();
}
template<std::size_t N>
typename std::enable_if<!nearest_foo_specialized<N>::value>::type call_nearest_foo() {
static_assert(N!=N, "No nearest foo() specialization found!");
}
int main() {
call_nearest_foo<7>(); // calls foo<5>()
call_nearest_foo<8>(); // calls foo<10>()
call_nearest_foo<11>(); // calls foo<10>()
call_nearest_foo<15>(); // calls foo<15>()
call_nearest_foo<25>(); // calls foo<15>()
// call_nearest_foo<26>(); // error: No nearest foo() (only searching up to 10 up / down)
}