4

Consider the following code that uses source_location:

// Preamble
#include <iostream>
#include <source_location>

// Using a function
constexpr std::size_t function(
    std::size_t line = std::source_location::current().line()
) noexcept {return line;}

// Using a constructor
struct construction {
    constexpr construction(
        std::size_t input = std::source_location::current().line()
    ) noexcept: line{input} {}
    std::size_t line;
};

// Using a template deduction guide
template <std::size_t Line>
struct wrapper {
    static constexpr std::size_t line = Line;
};
template <std::size_t index = std::source_location::current().line()>
wrapper() -> wrapper<index>;

// Main
int main(int argc, char* argv[]) {
    std::size_t f1 = function();
    std::size_t f2 = function();
    std::size_t c1 = construction().line;
    std::size_t c2 = construction().line;
    std::size_t w1 = wrapper().line;
    std::size_t w2 = wrapper().line;
    std::cout << f1 << " " << f2 << std::endl;
    std::cout << c1 << " " << c2 << std::endl;
    std::cout << w1 << " " << w2 << std::endl;
    return 0;
}

It produces the following output:

// GCC 12.2
28 29 // NOT EQUAL
30 31 // NOT EQUAL
23 23 // EQUAL

// CLANG 15.0
7 7   // EQUAL 
13 13 // EQUAL
23 23 // EQUAL

QUESTION:

  • What should be the correct result according to the standard (in particular in the case of the wrapper?)
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Vincent
  • 57,703
  • 61
  • 205
  • 388

1 Answers1

4

Let's start with the simple one: wrapper.

The way source_location::current() is defined to work is quite... specific to certain use cases. By default, the location that current refers to is its own location; the literal text containing the call to current.

[support.srcloc.cons]/1.2 defines the following exceptions to this rule:

Remarks: Any call to current that appears as a default member initializer ([class.mem]), or as a subexpression thereof, should correspond to the location of the constructor definition or aggregate initialization that uses the default member initializer. Any call to current that appears as a default argument ([dcl.fct.default]), or as a subexpression thereof, should correspond to the location of the invocation of the function that uses the default argument ([expr.call]).

You will notice that "default argument" points to a section about default function arguments.

Default template arguments are not default function arguments. Therefore, default template arguments are not on this list as exceptions. Therefore, current will always point to the location of the literal text in the code. Which will be the same for any template instantiation of wrapper that uses the default value.

The case of constructor and function are identical to each other. Constructors are just a kind of function, after all. And 1.2's "default argument" clause applies to both. Therefore, current in this context should refer to "the location of the invocation of the function that uses the default argument".

GCC complies with this. Clang does not. Clang is wrong.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Would there be a trick to make it work at the call site for wrapper? – Vincent Feb 02 '23 at 11:37
  • @Vincent: Probably not. `source_location::current` is pretty clear on how it works, and anything you might try to do is going to inevitably run into that problem. – Nicol Bolas Feb 04 '23 at 17:06