Question

Based on the answer in Detecting constexpr with SFINAE I'm trying to use SFINAE to check if a 'constexpr' is present in my class.

The problem is that the constexpr is a function pointer:

#include <type_traits>
#include <iostream>

typedef int (*ptr_t)();
int bar() { return 9; }

struct Foo {
    static constexpr ptr_t ptr = &bar;
};

namespace detail {
template <ptr_t>
struct sfinae_true : std::true_type {};

template <class T>
sfinae_true<T::ptr> check(int);

// Commented out to see why clang was not evaluating to true. This should only be
// a comment when debugging!
// template <class>
// std::false_type check(...);
}  // detail::

template <class T>
struct has_constexpr_f : decltype(detail::check<T>(0)) {};

int main(int argc, char *argv[]) {
    std::cout << has_constexpr_f<Foo>::value << std::endl;
    return 0;
}

It seems to work fine using gcc, but clang complains:

test.cxx:23:39: error: no matching function for call to 'check'
    struct has_constexpr_f : decltype(detail::check<T>(0)) {};
                                      ^~~~~~~~~~~~~~~~
test.cxx:26:22: note: in instantiation of template class 'has_constexpr_f<Foo>' requested here
        std::cout << has_constexpr_f<Foo>::value << std::endl;
                     ^
test.cxx:16:25: note: candidate template ignored: substitution failure [with T = Foo]: non-type template argument for template parameter of pointer type 'ptr_t' (aka 'int (*)()') must have its address taken
    sfinae_true<T::ptr> check(int);
                ~       ^
1 error generated.

Q1: Can anyone suggest a way of doing this which works both for Clang and GCC?

Q2: Is this a bug in gcc, clang or is this left undefined in the c++ standard?

Was it helpful?

Solution

That's not a bug in clang, but an unfortunate restriction of arguments for non-type template parameters of pointer type (see pointer as non-type template argument). Essentially, you can only use arguments of the form &something: [temp.arg.nontype]/1 (from n3797)

[if the template-parameter is a pointer, its argument can be] a constant expression (5.19) that designates the address of a complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, where the id-expression is the name of an object or function, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or [..]

[emphasis mine]

You can however, use a function pointer in a constant expression that has a non-pointer type, for example a boolean expression such as

T::ptr != nullptr

This works under clang++3.5 and g++4.8.2:

#include <type_traits>
#include <iostream>

typedef int (*ptr_t)();
int bar() { return 9; }

struct Foo0 {
    static constexpr ptr_t ptr = &bar;
};

struct Foo1 {
    static const ptr_t ptr;
};
ptr_t const Foo1::ptr = &bar;

struct Foo2 {
    static const ptr_t ptr;
};
//ptr_t const Foo2::ptr = nullptr;

namespace detail
{
    template <bool>
    struct sfinae_true : std::true_type {};

    template <class T>
    sfinae_true<(T::ptr != nullptr)> check(int);
    // the result of the comparison does not care

    template <class>
    std::false_type check(...);
}  // detail::

template <class T>
struct has_constexpr_f : decltype(detail::check<T>(0)) {};

int main(int argc, char *argv[]) {
    std::cout << std::boolalpha << has_constexpr_f<Foo0>::value << std::endl;
    std::cout << std::boolalpha << has_constexpr_f<Foo1>::value << std::endl;
    std::cout << std::boolalpha << has_constexpr_f<Foo2>::value << std::endl;
    return 0;
}

Note there's a difference between clang++ and g++ for the second output (Foo1): g++ says true, clang++ says false.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top