By K-ballo


2013-03-05 19:47:05 8 Comments

I'm working on upgrading some C++ code to take advantage of the new functionality in C++11. I have a trait class with a few functions returning fundamental types which would most of the time, but not always, return a constant expression. I would like to do different things based on whether the function is constexpr or not. I came up with the following approach:

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;
}

The extra int/... parameter is there so that if both functions are available after SFINAE, the first one gets chosen by overloading resolution.

Compiling and running this with Clang 3.2 shows:

regular: 0
constexpr: 1

So this appears to work, but I would like to know if the code is legal C++11. Specially since it's my understanding that the rules for SFINAE have changed.

2 comments

@TBBle 2017-04-30 00:16:46

Prompted by @marshall-clow, I put together a somewhat more-generic version of an type-trait for detecting constexpr. I modelled it on std::invoke_result, but because constexpr depends on the inputs, the template arguments are for the values passed in, rather than the types.

It's somewhat limited, as the template args can only be a limited set of types, and they're all const when they get to the method call. You can easily test a constexpr wrapper method if you need other types, or non-const lvalues for a reference parameter.

So somewhat more of an exercise and demonstration than actually-useful code.

And the use of template<auto F, auto... Args> makes it C++17-only, needing gcc 7 or clang 4. MSVC 14.10.25017 can't compile it.

namespace constexpr_traits {

namespace detail {

// Call the provided method with the provided args.
// This gives us a non-type template parameter for void-returning F.
// This wouldn't be needed if "auto = F(Args...)" was a valid template
// parameter for void-returning F.
template<auto F, auto... Args>
constexpr void* constexpr_caller() {
    F(Args...);
    return nullptr;
}

// Takes a parameter with elipsis conversion, so will never be selected
// when another viable overload is present
template<auto F, auto... Args>
constexpr bool is_constexpr(...) { return false; }

// Fails substitution if constexpr_caller<F, Args...>() can't be
// called in constexpr context
template<auto F, auto... Args, auto = constexpr_caller<F, Args...>()>
constexpr bool is_constexpr(int) { return true; }

}

template<auto F, auto... Args>
struct invoke_constexpr : std::bool_constant<detail::is_constexpr<F, Args...>(0)> {};

}

Live demo with use-cases on wandbox

@Xeo 2013-03-06 00:11:41

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.

@K-ballo 2013-03-06 05:19:54

That quote from the standard clears all my doubts, thank you.

@Johannes Schaub - litb 2013-03-06 08:35:12

unfortunately i dont think it is that clear. a "default template argument" could not be specified for functions in C++03, yet that text was present there too. the text only talks about default arguments on functions, and not on class templates. which makes me believe it doesnt talk about template arguments, but function arguments.

@Johannes Schaub - litb 2013-03-06 08:37:34

i disagree that this code is clearly valid since it is not clear that the error is in an immediate context and it is not at all clear whether delayed instantiation happens at all.

@Xeo 2013-03-06 09:23:26

@Johannes: Ooh, good point. I kinda had my doubt if OP's code was actually valid, until I stumbled opon that part of the standard. Mind joining me in the Lounge to discuss this?

@Johannes Schaub - litb 2013-03-06 09:30:51

@xeo im in the bus on the way to work. i wrote my comments when i still was getting up on my phone. so unfortunately i can't join the lounge :-)

@Johannes Schaub - litb 2013-03-06 09:36:14

what is clear is that the standard says that for the purpose of instantiation, default function arguments of temploids are considered to be template definitions (and thereby gain independent instantiation capability).

@Xeo 2013-03-06 10:01:02

@Johannes: I wasn't sure what exactly it means for a "default template argument" to also be a definition. This is complicated. :|

@Johannes Schaub - litb 2013-03-06 10:51:06

no. default function arguments are considered to be definitions. not default template arguments, as far as i am aware. i recommend to post a new question to clarify this matter.

@Xeo 2013-03-06 13:47:08

@Johannes: Done, see the note at the top of the answer.

Related Questions

Sponsored Content

8 Answered Questions

[SOLVED] Difference between `constexpr` and `const`

1 Answered Questions

[SOLVED] constexpr algorithm all_of compiler error

14 Answered Questions

[SOLVED] When should you use constexpr capability in C++11?

  • 2011-01-20 14:07:06
  • Warren P
  • 109112 View
  • 327 Score
  • 14 Answer
  • Tags:   c++ c++11 constexpr

7 Answered Questions

[SOLVED] constexpr overloading

4 Answered Questions

[SOLVED] const vs constexpr on variables

2 Answered Questions

[SOLVED] std::result_of failing for void return type

1 Answered Questions

[SOLVED] is_enum causing incorrect behavior for SFINAE application?

1 Answered Questions

[SOLVED] SFINAE - Detect constructor with one argument

1 Answered Questions

[SOLVED] How to avoid this sentence is false in a template SFINAE?

Sponsored Content