66

The C++20 feature std::source_location is used to capture information about the context in which a function is called. When I try to use it with a variadic template function, I encountered a problem: I can't see a place to put the source_location parameter.

The following doesn't work because variadic parameters have to be at the end:

// doesn't work
template <typename... Args>
void debug(Args&&... args,
           const std::source_location& loc = std::source_location::current());

The following doesn't work either because the caller will be screwed up by the parameter inserted in between:

// doesn't work either, because ...
template <typename... Args>
void debug(const std::source_location& loc = std::source_location::current(),
           Args&&... args);

// the caller will get confused
debug(42); // error: cannot convert 42 to std::source_location

I was informed in a comment that std::source_location works seamlessly with variadic templates, but I struggle to figure out how. How can I use std::source_location with variadic template functions?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
L. F.
  • 19,445
  • 8
  • 48
  • 82
  • 2
    Perhaps make `debug` a macro that will call the real "debug" function with the `std::source_location::current()` call at the correct argument position (first)? – Some programmer dude Aug 18 '19 at 18:34
  • Regarding the removed comments that resulted in the edit: can't we have auto function arguments in templates in [c++20](https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template)? – eerorika Aug 18 '19 at 18:35
  • 1
    @Someprogrammerdude That will work correctly, but I consider that only a fallback if there's no better method. Using a macro defeats the purpose of `std::source_location` in some way IMO :( – L. F. Aug 18 '19 at 18:37
  • @eerorika Yes, `auto` is allowed in the parameter, but then we can provide `42` or `"foo"` as the source location. – L. F. Aug 18 '19 at 18:38
  • @L.F. Could be an useful customisation point to let the caller use a special function name for example. – eerorika Aug 18 '19 at 18:42
  • @eerorika I didn't intend to do that, but sounds interesting. For the purpose of this question, consider it as a typo :) – L. F. Aug 18 '19 at 18:45
  • Source location looks constexpr, can something be done by adding it in the template list? (Pointer to?) – JVApen Aug 18 '19 at 19:14
  • @JVApen It is `consteval`, actually! But it does not seem specified what happens if it is called as a template parameter. – Acorn Aug 18 '19 at 19:20
  • @Acorn: https://en.cppreference.com/w/cpp/experimental/source_location/source_location still indicates constexpr – JVApen Aug 18 '19 at 19:22
  • @JVApen Look into https://en.cppreference.com/w/cpp/utility/source_location/current or http://eel.is/c++draft/support.srcloc#source_location.syn -- I am referring to `current()` – Acorn Aug 18 '19 at 19:22
  • So in short, something has to be done at compile time (and that's now forced). Question still holds – JVApen Aug 18 '19 at 19:24
  • @L.F.: "*Using a macro defeats the purpose of std::source_location in some way IMO*" Nonsense. `source_location` is an actual C++ type and therefore behaves like regular C++ objects that store values. You can pass them around, store their values, and so forth, unlike macros like `__LINE__`. – Nicol Bolas Aug 18 '19 at 19:39
  • @NicolBolas I agree with your comment, except that I don’t see how the sentence is nonsense? Because the rest of your comment seems to actually support the nonsense sentence. – L. F. Aug 18 '19 at 20:48
  • @L.F.: All of the advantages of `source_location` *still exist* whether the object is created by a macro or by regular C++ logic. That's my point; it in no way invalidates `source_location` if you have a `DEBUG` macro that calls your variadic function with a hand-invoked `source_location::current`. – Nicol Bolas Aug 18 '19 at 21:08
  • To followup my own remark, I got something compiling with the only implementation that I could find at CompilerExplorer. It doesn't give the right results. For those interested: https://godbolt.org/z/5fc4ed – JVApen Aug 18 '19 at 21:09
  • 1
    @NicolBolas You are right, being a regular object that can be passed around with its value unchanged is definitely an advantage of source_location. But I’d say the ability to get rid of macros is also an advantage, and that is the purpose I “intended” to defeat. Therefore I agree that the sentence is incomplet, but it is not incorrekt, is it? So it didn’t make much sense to me that it is nonsense. (I don’t know how to produce bad formatting here ...) – L. F. Aug 18 '19 at 21:13

7 Answers7

67

The first form can be made to work, by adding a deduction guide:

template <typename... Ts>
struct debug
{    
    debug(Ts&&... ts, const std::source_location& loc = std::source_location::current());
};

template <typename... Ts>
debug(Ts&&...) -> debug<Ts...>;

Test:

int main()
{
    debug(5, 'A', 3.14f, "foo");
}

DEMO

hellow
  • 12,430
  • 7
  • 56
  • 79
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Priotr, I don't understand that syntax. Could you explain it a little? – Silicomancer Oct 21 '19 at 11:16
  • 1
    @Silicomancer It is a [deduction guide](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction#User-defined_deduction_guides). – L. F. Oct 21 '19 at 11:29
  • 1
    This seems like a trivial guide. Why does it change the argument deductions? – KitsuneYMG Aug 26 '21 at 14:38
  • @KitsuneYMG Simply put, `Ts…` are deduced by the deduction guide and hence known by the time overload resolution happens on the constructor, so the constructor can have default arguments. – L. F. Jun 08 '22 at 16:26
21

If your function has a fixed parameter before the variadiac arguments, like a printf format string, you could wrap that parameter in a struct that captures source_location in its constructor:

struct FormatWithLocation {
  const char* value;
  std::source_location loc;

  FormatWithLocation(const char* s,
                     const std::source_location& l = std::source_location::current())
      : value(s), loc(l) {}
};

template <typename... Args>
void debug(FormatWithLocation fmt, Args&&... args) {
  printf("%s:%d] ", fmt.loc.file_name(), fmt.loc.line());
  printf(fmt.value, args...);
}

int main() { debug("hello %s\n", "world"); }
ivank
  • 211
  • 2
  • 4
  • 2
    I like this solution (vs the accepted answer with the deduction guide): 1.) it allows you to pass source_location manually if you need to 2.) the function stays a function (and does not become a struct/constructor call) which allows you to add [[ noreturn ]] --> useful if this is supposed to log a fatal error – Xatian May 15 '21 at 08:12
8

Just put your arguments in a tuple, no macro needed.

#include <source_location>
#include <tuple>

template <typename... Args>
void debug(
    std::tuple<Args...> args,
    const std::source_location& loc = std::source_location::current())
{
    std::cout 
        << "debug() called from source location "
        << loc.file_name() << ":" << loc.line()  << '\n';
}

And this works*.

Technically you could just write:

template <typename T>
void debug(
    T arg, 
    const std::source_location& loc = std::source_location::current())
{
    std::cout 
        << "debug() called from source location "
        << loc.file_name() << ":" << loc.line()  << '\n';
}

but then you'd probably have to jump through some hoops to get the argument types.


* In the linked-to example, I'm using <experimental/source_location> because that's what compilers accept right now. Also, I added some code for printing the argument tuple.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 7
    "*this works just fine*" You mean, besides the fact that you have to put the values in a tuple? And therefore have to deal with a lot of pointless syntax to actually extract and use them for their intended purpose? – Nicol Bolas Aug 18 '19 at 19:43
  • @NicolBolas: s/a lot of/a bit of/ ; But - see edit. – einpoklum Aug 18 '19 at 19:43
  • That all depends on what you're doing with them. In a variadic template, formatting all of the values to a stream is trivial and easily readable. In your version, it is neither. It's *doable*, but not pretty. – Nicol Bolas Aug 18 '19 at 19:44
  • @NicolBolas: You might prefer [that](http://coliru.stacked-crooked.com/a/3fa3ccc74e21fd36), but I would say it is just stylistic "problem" to iterate over tuple/variadic template. – Jarod42 Aug 18 '19 at 20:24
5
template <typename... Args>
void debug(Args&&... args,
           const std::source_location& loc = std::source_location::current());

"works", but requires to specify template arguments as there are non deducible as there are not last:

debug<int>(42);

Demo

Possible (not perfect) alternatives include:

  • use overloads with hard coded limit (old possible way to "handle" variadic):

    // 0 arguments
    void debug(const std::source_location& loc = std::source_location::current());
    
    // 1 argument
    template <typename T0>
    void debug(T0&& t0,
               const std::source_location& loc = std::source_location::current());
    
    // 2 arguments
    template <typename T0, typename T1>
    void debug(T0&& t0, T1&& t1,
               const std::source_location& loc = std::source_location::current());
    
    // ...
    

    Demo

  • to put source_location at first position, without default:

    template <typename... Args>
    void debug(const std::source_location& loc, Args&&... args);
    

    and

    debug(std::source_location::current(), 42);
    

    Demo

  • similarly to overloads, but just use tuple as group

    template <typename Tuple>
    void debug(Tuple&& t,
               const std::source_location& loc = std::source_location::current());
    

    or

    template <typename ... Ts>
    void debug(const std::tuple<Ts...>& t,
               const std::source_location& loc = std::source_location::current());
    

    with usage

    debug(std::make_tuple(42));
    

    Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I like your first alternative the best. While it's ugly code, it's the most convenient to use, and that's what's most important. – einpoklum Aug 18 '19 at 21:19
3

Not a great solution but... what about place the variadic arguments in a std::tuple?

I mean... something as

template <typename... Args>
void debug (std::tuple<Args...> && t_args,
            std::source_location const & loc = std::source_location::current());

Unfortunately, this way you have to explicitly call std::make_tuple calling it

debug(std::make_tuple(1, 2l, 3ll));
max66
  • 65,235
  • 10
  • 71
  • 111
  • 1
    @L.F. - sorry: maybe I've misunderstood: do you mean that do you want substitute a variadic macro with a template variadic function? – max66 Aug 18 '19 at 18:59
  • My original question doesn’t make sense at all. I have updated my question to make the actual question stand out. Ignore the variadic macros. Sorry! – L. F. Aug 18 '19 at 19:17
  • @L.F. - I see... well, my answer remain almost the same but the needs of explicitly call `std::make_tuple()` make it less interesting. – max66 Aug 18 '19 at 19:41
1

You can try make it:

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

struct log
{
  log(std::experimental::source_location location = std::experimental::source_location::current()) : location { location } {}

  template<typename... Args>
  void operator() (Args... args)
  {
    std::cout << location.function_name() << std::endl;
    std::cout << location.line() << std::endl;
  }

  std::experimental::source_location location;
};

int main() 
{
  log()("asdf");
  log()(1);
}

DEMO

chatlanin
  • 49
  • 1
  • 7
  • [A code-only answer is not high quality](//meta.stackoverflow.com/questions/392712/explaining-entirely-code-based-answers). While this code may be useful, you can improve it by saying why it works, how it works, when it should be used, and what its limitations are. Please [edit] your answer to include explanation and link to relevant documentation. – Muhammad Mohsin Khan Mar 15 '22 at 15:24
1

If you can accept the use of macros, you can write this to avoid explicitly passing in std::source_ location::current()

template <typename... Args>
void debug(const std::source_location& loc, Args&&... args);

#define debug(...) debug(std::source_location::current() __VA_OPT__(,) __VA_ARGS__)
quad
  • 11
  • 2