7

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.

cigien
  • 57,834
  • 11
  • 73
  • 112
Mr. Anderson
  • 1,609
  • 1
  • 13
  • 24
  • 4
    Hmm, where and why would you use something like this? Perhaps a more elegant solution to your initial problem is out there. – DeiDei Mar 28 '17 at 14:48
  • Well, I can't use overloads and SFINAE because I have a non-type template parameter. I can however add an unnamed `std::integral_constant` to the functions. But I was hoping there might be a cleaner solution out there... – Mr. Anderson Mar 28 '17 at 14:59
  • 1
    non-type template parameters don't prevent you from using overloads or SFINAE. It's still not clear why you would want to detect this. – aschepler Mar 28 '17 at 15:03
  • It is not clear what the actual problem here is. – shargors Mar 28 '17 at 15:10
  • 1
    Let's say I'm developing a header-based library that allows the user to specialize `foo` as many times as he likes, then call a template function in the library with a certain `size_t` template argument, and this function will delegate to the function that was specialized with the nearest number. Is that possible? – Mr. Anderson Mar 28 '17 at 15:10
  • I'm removing the [c++11] tag from this question since it unnecessarily constrains solutions to that revision, which doesn't seem relevant to the question asked. – cigien May 23 '22 at 15:52

2 Answers2

4

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
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • 3
    One step further: declare a dummy `using im_not_specialized = void;` in the base template, and detect it later. Now the user does not have to do anything extra :) – Quentin Mar 28 '17 at 15:18
  • @Quentin - not sure to undestand... `im_not_specialized` should be added to `is_specialized` or used to substitute it? And when should be used `im_not_specialized`? – max66 Mar 28 '17 at 15:25
  • It replaces `is_specialized`. Then you can use SFINAE to detect whether the `foo>` you're looking at contains the typedef (then it's from the base template) or not (then it has been specialized). – Quentin Mar 28 '17 at 15:27
  • @Quentin - I see... interesting solution; you should propose it as an answer – max66 Mar 28 '17 at 15:29
  • 1
    Well, it still hinges on using functors. I'm searching for a solution that keeps the function templates. – Quentin Mar 28 '17 at 15:30
  • 1
    @Quentin - I look forward to see your template based solution; I'm not very good in SFINAE but, if you don't worry, I try to add, in my answer, an example based on your `im_not_specialized` – max66 Mar 28 '17 at 15:38
  • @Quentin Interesting. But if i'd be using functors that I could easily test for `sizeof>` in SFINAE. This will fail for an `N` that hasn't been introduced with a specialization. – Mr. Anderson Mar 28 '17 at 15:39
  • @max66 Go ahead :) – Quentin Mar 28 '17 at 15:39
  • There is always the possibility a user might write `template<>class foo<42>:foo<41>{...}`. To be more robust, you need `using default_for_type = foo;` so you can check if this is the default for the right type. – Marc Glisse Oct 26 '18 at 21:51
2

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)
}
Turtlefight
  • 9,420
  • 2
  • 23
  • 40