Typeless Functions (part 2)

The topic of this blog is an experimental language feature called Parametric Expressions, but it's fun to call them "type less" functions as they are like functions but have no type, and using them can lead to less typing on the keyboard.

In the previous post we looked at how parametric expressions can return unexpanded parameter packs. It was brought to my attention that people don't know what parametric expressions are. For a complete description check out the proposal paper here: P1221.

However, for the sake of TLDR, I will start this post off with a brief explanation and the declaration syntax. Then I'll demonstrate how parametric expressions can be used to pass id-expressions which enable users to write more concise code without adding special operators to the language. For this post specifically we'll cover concise forwarding.

Basically parametric expressions are a hygienic macro that occur during semantic analysis and template instantiation. To prevent duplicate evaluation, the default behaviour is to bind arguments to parameters which are evaluated as they appear from left to right.

Here is a very simple example of the declaration syntax:

using foo(auto x) {
  return x;
}

It is important to note that the auto in the parameter declaration is not a type but a constraint. Please ignore it for now and know that parameters are implicitly auto&& as is typically seen in generic code.

When called, foo is evaluated with its own RAII scope without any kind of function call. Think of it as behaving like a compound statement that returns a value.

This can be called just like a function:

int x = foo(5);

Simple right? This is the default behaviour.

There are an abundance of use cases, however, where we sometimes want the parameter to behave more like a macro parameter. This means we can control evaluation and work with placeholder expressions, intermediate expressions that might not refer to an actual value. The name of an overloaded function is a good example.

For these, Parametric Expressions adds the using keyword to specify it in the parameter declaration.

using foo(using auto x) {
  return x;
}

This version of foo behaves the same as before, but now there is no need for a RAII scope because there is no actual parameter, but x is substituted with the input expression everywhere it is found in the body. This allows for a transparent AST transformation so the compiler treats it as the expression in the return statement.

Concise Forwarding

Consider the following Tony Table:

template <typename X>
decltype(auto) foo(X&& x) {
  return std::forward<X>(x);
}
using foo(using auto x) {
  return x;
}

Or if you want to keep it as a function:

template <typename X>
decltype(auto) foo(X&& x) {
  return std::forward<X>(x);
}
template <typename X>
decltype(auto) foo(X&& x) {
  return fwd(x);
}

It's important to know that id-expressions are always treated as lvalues to prevent implicitly double moving objects. However, there is an exception to this. When the id-expression is wrapped by itself in decltype it has the same type as the parameter it refers to.

Knowing this, we can use using parameters to make a more concise interface for forwarding:

using fwd(using auto x) {
  return static_cast<decltype(x)>(x);
}

Note that both xs are substituted with the input expression.

This is quite useful in generic lambdas where forwarding is a bit more of a pain because you don't have a name for the type.

auto fn = [](auto&& x, auto&& y) {
  return std::pair{std::forward<decltype(x)>(x),
                   std::forward<decltype(y)>(y)};
};
auto fn = [](auto&& x, auto&& y) {
  return std::pair{fwd(x), fwd(y)};
};

Summary

There is actually much more that we can do with id-expressions. In the next post we will look at hiding lambda syntax when we just want a call to an overloaded function.

If you want to try out the fwd example above, it is available on Compiler Explorer: https://godbolt.org/z/uPO4zl

Thanks for looking at this!

Jason Rice

Follow on Twitter
@JasonRice_

Parametric Expressions

Parametric Expressions are a hygienic macro like language feature proposed for C++.

Check it out! P1221