6

In the code below, if here() stops being consteval (either full RT, or constexpr), then line() is the line of invocation of f() inside main(). But with consteval it's the definition of f(). Where does this discrepancy come from?

#include <experimental/source_location>
#include <iostream>

consteval std::experimental::source_location here(
    std::experimental::source_location loc = std::experimental::source_location::current())
{
   return loc;
}

void f(const std::experimental::source_location& a = here())
{
   std::cout << a.line() << std::endl; // will either print 17, or 10
}

int main()
{
   f();
}

Godbolt link

Boann
  • 48,794
  • 16
  • 117
  • 146
Marcin Zdun
  • 142
  • 1
  • 8
  • 1
    Default arguments are substituted at the call site (if used). If the expression was already constevaled it will have the same value no matter the call site, arguably? As different translation units may call the same `f` function. That would loosely/poorly explain _why_, but I agree that this would come as surpising. – dfrib Jun 29 '20 at 13:00
  • It's important to note that what you're trying to do (replace `source_location::current` with something equivalent to it) is *not going to work*. This function is *supposed* to always return the location of the code where `source_location::current` resides, not the location from which it is *called*. The correct answer for *any* call to `here` is supposed to be line 4. There is no way to make what you're trying to do work; you have to call the function exactly from the place you desire to get the current location of. – Nicol Bolas Jun 29 '20 at 13:25
  • From my own experiments source location is not yet well implemented by GCC. consteval or not you may find variations of the reported source code line. – Oliv Jun 29 '20 at 16:43
  • @NicolBolas, try the godlink, replace ```consteval``` with ```constexpr``` (or remove it altogether) and see the change. Now ```here()``` turns into alias for ```source_location::current()``` -- my understanding is it happens _because_ ```a = here()``` is evaluated at call to ```f()```, which in turn evaluates ```loc = source_location::current()``` in the very same place, which in turn evaluates all the default arguments to ```current()``` with values from handful of builtin, intrinsic "functions". – Marcin Zdun Jun 29 '20 at 17:08
  • @Oliv, the GCC/Clang ```__builtin_FILE```/```_FUNCTION```/```_LINE``` are here for some time, ```source_location::current``` is a way to standardize them... – Marcin Zdun Jun 29 '20 at 17:11
  • @MarcinZdun Those builtins are here for some time, but source_location is still in the experimental folder. – Oliv Jun 29 '20 at 17:13
  • @Oliv Yeah, but it also means the place of evaluation was at the place of call even before ```source_location```, or they would not be able to work. It's the ```consteval``` which changes the game (i.e. the place of evaluation and the place of definition are one and the same, apparently) – Marcin Zdun Jun 29 '20 at 20:03
  • @dfri: this was compelling explanation until I tried to use `consteval` function in two different calls. See the example simplified to [__builtin_LINE intrinsic](https://godbolt.org/z/QX4UP5). There are two evaluations now, each on the line, where I introduce each new function using the `consteval`... Now I'm not sure what is happening. Maybe your explanations is "still good" and this is a bug in GCC? – Marcin Zdun Jul 02 '20 at 07:58

1 Answers1

0

Here is my understanding:

The default arguments are substituted at call site and evaluated at each call. See the following code as example:

#include <iostream>

int count() {
    static int counter = 0;
    return ++counter;
}

void foo(int value = count())
{
    std::cout << value << "\n";
}

int main()
{
   foo();
   foo();
}

See in godbolt

The output is

1
2

Which proves count() has been called twice, and the main() body is effectively equivalent to :

foo(count());
foo(count());

Now let's go back to your example. When here() is not consteval, then your call to f() is equivalent to f(here()) which in turn is equivalent to f(here(std::experimental::source_location::current())) and this will return the line of the call to f(), which is 17.

However, if here() is consteval, when the compiler reads the declaration of f(), it must immediately evaluate here() which returns a certain std::experimental::source_location whose line() equals 10 at this point (let's call it default_location for the purpose of the explanation), therefore when you call f(), the default argument is already evaluated to default_location, and the call is effectively equivalent to f(default_location)

Annyo
  • 1,387
  • 9
  • 20
  • The value of the `default_location` you propose is wacky to say the least. See modified [Godbolt](https://godbolt.org/z/QX4UP5), where there are two different functions using `consteval` default initializer -- and they are calculated at the place of the (declaration? definition?) of the function -- see `f()` and `g()` in my example and then there is non-`consteval` `line()` and `h()`, which uses it and has more expected results/side effects... – Marcin Zdun Jul 02 '20 at 07:55
  • Yes, because the compiler sees the definition (I think you're right, it's a definition) of `f()`, sees the default argument is `here()`, but `here()` is `consteval`, so it must evaluate it immediately, so the default argument actually becomes `13` instead of `here()`. The same happens for `g()`, where the default argument is immediately evaluated to `19` instead of `here()`. For the function `h()` however, `line()` is not `consteval`, so it has no reason to evaluate it immediately, and keeps `line()` itself as the default argument. – Annyo Jul 02 '20 at 08:18