Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE automatically check that function body compiles without explicit constraints

I often use SFINAE to remove a function from the overload set if the function body does not make sense(i.e. does not compile). Would it be possible to add to C++ a simple require statement?

For example, let's have a function:

template <typename T>
T twice(T t) {
  return 2 * t;
}

Then I get:

twice(1.0);
twice("hello");  // Error: invalid operands of types ‘int’ and ‘const char*’ to binary ‘operator*’

I want to get an error that says that there is not function twice for argument of type const char *

I would love to write something like:

template <typename T>
requires function_body_compiles
T twice(T t) {
  return 2 * t;
}

Then I would get

twice(1.0);
twice("hello");  // Error: no matching function for call to ‘twice2(const char [6])’

More motivation: I was watching the talk The Nightmare of Move Semantics for Trivial Classes and his final SFINAE is basically saying: use this constructor when it does compile. For a more complicated constructor writing the correct SFINAE would be a nightmare.

Do you think that adding requires function_body_compiles to c++ would make sense? Or is there a fundamental problem I'm missing? How badly could this be abused or misused?

like image 780
tom Avatar asked Nov 23 '18 06:11

tom


3 Answers

The biggest reason why we don't have this feature is that it is hard.

It is hard, because it requires compilers be able to compile nearly arbitrary C++ code, get errors, then back out cleanly.

Existing C++ compilers where not all designed to do this. In fact, it took MSVC most of a decade to have reasonably compliant decltype SFINAE support.

Doing so for full function bodies would be even harder.


Now, even if it was easy, there are reasons not do do this. It mixes implementation and interface in a pretty horrible way.

Rather than go this way, the C++ committee is moving in a completely different direction.

Concepts are the idea that you can express requirements about types in sensible, usually named ways. They are coming in c++20.

As another answer mentions,

template <typename T> requires requires(T t) { { 2 * t } -> T; }
T twice(T t) {
  return 2 * t;
}

is a way to do it, but that way is considered bad form. Instead, you should write a concept "can be multiplied by an integer and get the same type back".

template<typename T>
concept IntegerScalable = requires(T t) {
  { 2 * t } -> T;
};

we can then

template <IntegerScalable T>
T twice(T t) {
  return 2 * t;
}

and we are done.

A desired next step is called "checked concepts". In checked concepts, the concept it converted into a set of compile-time interfaces for your type T.

Then the body of the function is checked to ensure nothing is done to anything of type T that isn't a requirement of a concept.

Using a theoretical future checked concept,

template <IntegerScalable T>
T twice(T t) {
  T n = 7;
  if (n > t) return n;
  return 2 * t;
}

this would be rejected by the compiler when compiling the template even before a call to the template was done, because the concept IntegerScalable didn't guarantee that you could either initialize a T with an integer, nor that you could compare one T to another with >. Plus I think the above requires move-construction.


There is a hack you can do today.

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

then your code can be written as:

template<class T>
auto twice(T t)
RETURNS( 2 * t )

and you'll get a SFINAE friendly version of twice. It will also be as noexcept as it can be.

A variant of this using => to replace RETURNS and some other stuff was proposed by @Barry, but it has been a year since I've seen it move.

Meanwhile, RETURNS does most of the heavy lifting.

like image 91
Yakk - Adam Nevraumont Avatar answered Oct 29 '22 03:10

Yakk - Adam Nevraumont


There was a [proposal] filed by Barry Revzin for exactly what you are asking, but in context of lambda expressions. As it requires constructing lambda the syntax would be a bit different:

auto twice = [](auto t) => 2 * t; //sfinae friendly

or even:

auto twice = 2 * $0;

Nevertheless the status of this proposal is still uncertain. You can check it [here].

However in case of constructor I'm not sure if there would be a way to apply such a construct even if the proposal get accepted. Nevertheless if someone saw the need in case of lambda expressions there is probably potential for language development in the general case.

like image 2
W.F. Avatar answered Oct 29 '22 02:10

W.F.


You can do to some extent what you want with requires-expressions (https://godbolt.org/z/6FDT45):

template <typename T> requires requires(T t) { { 2 * t } -> T; }
T twice(T t) {
  return 2 * t;
}

int main()
{
twice(1.0);
twice("hello"); // Error: Constraints not satisfied
}

As you noted in the comments, a helper function cannot be used to avoid writting the function body twice, because errors in the implementation are not found until instantiation time. However, requires expressions benefit from advantages over decltype(expr) trailing return types:

  • They are not limited to return types.
  • There can be as many expressions as needed.

What you would like to have is referred to as "concept definition checking". Bjarne Stroustrup discusses why it is missing in the concepts design in the paper P0557R0 (Section 8.2).

like image 2
metalfox Avatar answered Oct 29 '22 04:10

metalfox