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
How to Run a Child Process That Requires Elevation and Wait
Which Headers in the C++ Standard Library Are Guaranteed to Include Another Header
Why Is My Helloworld Function Not Declared in This Scope
Receiving Chunked Http Data with Winsock
Gcc C++ "Hello World" Program -> .Exe Is 500Kb Big When Compiled on Windows. How to Reduce Its Size
Check Variadic Templates Parameters for Uniqueness
Passing References to Pointers in C++
Is the Pass-By-Value-And-Then-Move Construct a Bad Idiom
How Is a Vector's Data Aligned
How to Get a Random Element from a C++ Container
Detecting Constexpr with Sfinae
Explicit Specialization in Non-Namespace Scope Does Not Compile in Gcc
How to Know If a Pointer Points to the Heap or the Stack
Understanding Boost.Spirit's String Parser