Detecting Constexpr with Sfinae

Detecting constexpr with SFINAE

NOTE: I opened a question here about whether OPs code is actually valid. My rewritten example below will work in any case.


but I would like to know if the code is legal C++11

It is, although the default template argument may be considered a bit unusual. I personally like the following style better, which is similar to how you (read: I) write a trait to check for a function's existence, just using a non-type template parameter and leaving out the decltype:

#include <type_traits>

namespace detail{
template<int> struct sfinae_true : std::true_type{};
template<class T>
sfinae_true<(T::f(), 0)> check(int);
template<class>
std::false_type check(...);
} // detail::

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

Live example.


Explanation time~

Your original code works because a default template argument's point of instantiation is the point of instantiation of its function template, meaning, in your case, in main, so it can't be substituted earlier than that.

§14.6.4.1 [temp.point] p2

If a function template [...] is called in a way which uses the definition of a default argument of that function template [...], the point of instantiation of the default argument is the point of instantiation of the function template [...].

After that, it's just usual SFINAE rules.


† Atleast I think so, it's not entirely clear in the standard.

Using SFINAE to check whether function is constexpr or not

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

Clang issue: Detecting constexpr function pointer with SFINAE

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.

Why is sfinae on if constexpr not allowed?

Your use of pointer to member function is a bad idea; if foo is overloaded, it spuriously fails (you have a foo, but not just one). Who really wants "do you have exactly one foo"? Almost nobody.

Here is a briefer version:

template<class T>
using dot_foo_r = decltype( std::declval<T>().foo() );

template<class T>
using can_foo = can_apply<dot_foo_r, T>;

where

namespace details {
template<template<class...>class, class, class...>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

Now, writing dot_foo_r is a bit annoying.

With constexpr lambdas we can make it less annoying and do it inline.

#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }

It does need the RETURNS macro, at least until @Barry's submission to [](auto&&f)RETURNS(f()) be equivalent to [](auto&&f)=>f().

We then write can_invoke, which is a constexpr variant of std::is_invocable:

template<class F>
constexpr auto can_invoke( F&& f ) {
return [](auto&&...args)->std::is_invocable<F(decltype(args)...)>{
return {};
};
}

This gives us:

if constexpr(
can_invoke([](auto&&var) RETURNS(var.foo()))(var)
) {
var.foo();
}

or using @Barry's proposed C++20 syntax:

if constexpr(can_invoke(var=>var.foo())(var)) {
var.foo();
}

and we are done.

The trick is that RETURNS macro (or => C++20 feature) lets us do SFINAE on an expression. The lambda becomes an easy way to carry that expression around as a value.

You could write

    [](auto&&var) ->decltype(var.foo()) { return var.foo(); }

but I think RETURNS is worth it (and I don't like macros).

Checking for constexpr in a concept

I think the expected concept can be created using std::bool_constant, which has the property that the failure of substitution in its argument of not-constant expression is not a compilation error, but just makes the concept false.

The proposed solution is

#include <concepts>
#include <type_traits>

template<typename T>
concept HasConstexprVoidBar =
requires(T t) {
{ t.bar() } -> std::same_as<void>;
{ std::bool_constant<(T{}.bar(), true)>() } -> std::same_as<std::true_type>;
};

struct A {
constexpr void bar() {}
};

struct B {
void bar() {}
};

int main() {
// concept check passes for constexpt A::bar()
static_assert( HasConstexprVoidBar<A> );
// concept check fails for B::bar()
static_assert( !HasConstexprVoidBar<B> );
}

Here the concept HasConstexprVoidBar is verified successfully for struct A having constexpr void method bar and evaluates to false for struct B having not-constexpr method.

Demo: https://gcc.godbolt.org/z/nsx9z99G4



Related Topics



Leave a reply



Submit