1

Can this be made to display the line number of the actual call site, like the macro does ?

#include <iostream>
#include <type_traits>
#include <source_location>
#include <cassert>
using namespace std;

namespace myLib{

template<typename Enumt, typename... Ts> // alert bad calls, throw
constexpr void setValsF(const unsigned line, Ts const&... args){
  cout << "line #"<<line<<": illegal call to setVals()\n";
  assert(is_enum_v<Enumt>);
}

template<class... Enumt, class = common_type_t<Enumt...> > 
requires conjunction_v<is_enum<Enumt>...>
constexpr void setValsF(const unsigned line, Enumt&... vals){
  cout << "line #"<<line<<": legal call to setVals()\n";
}
 
}// myLib

int main(){

  enum enumVals : short { a = 0, b, c, count };
  
  // #define setVals(...) myLib::setValsF<enumVals>(__LINE__ __VA_OPT__(,__VA_ARGS__))
  constexpr auto setVals = [](auto&&... args) {  
    myLib::setValsF<enumVals>(source_location::current().line(), args...); };
  
  setVals(a,b); // legal
  setVals(b,"text"); // illegal
}

run

.. keeping a nice simple API ( setVals(a,b) ), that is.

It would work :

  • with a defaulted argument to the function template, but for the parameter pack. Couldn't figure a deduction guide in the presence of more than one template parameter.
  • or if current() was the default constructor to source_location !

I resorted to runtime handling of bad calls (to certain APIs) as a courtesy to my library users, as my very specific insult is wayyy nicer than the indecipherable blob the compiler vomits.

ExpertNoob1
  • 900
  • 1
  • 8
  • 17
  • The normal trick is to put the default argument into a parameter's constructor. – chris Jan 31 '23 at 22:08
  • Yup. That's the nicest way out. Could not adapt `FormatWithLocation` from https://stackoverflow.com/questions/57547273/how-to-use-source-location-in-a-variadic-template-function to my case with multiple template parameters :( – ExpertNoob1 Jan 31 '23 at 22:17
  • 3
    I'm not sure why you can't just use `static_assert()` if all that you want to do is to display error message... – sklott Jan 31 '23 at 22:29
  • @sklott : the first template is just a trap that hands the dead fly to a dedicated `libAlert()` with some info (depending on previous user init choices) to form a precise error message. Not sure how a `static_assert` would help with the originating line issue. – ExpertNoob1 Feb 01 '23 at 09:06
  • 2
    Would this: https://godbolt.org/z/GrY6njMda be enough or is it too verbose for your taste? – Bob__ Feb 01 '23 at 14:58
  • @Bob__ Pretty neat workaround! – Ted Lyngmo Feb 01 '23 at 17:54
  • @Bob__ that's brilliant ! I basically lost hope on this, and now I'm scratching my head as I jump with excitement looking at the output window. The left window though.. looks a bit above my pay-grade, would you care to explain the voodoo in an complete answer ? – ExpertNoob1 Feb 01 '23 at 20:45
  • @TedLyngmo I'm afraid that clang [doesn't agree](https://godbolt.org/z/WqY31dKcj)... – Bob__ Feb 01 '23 at 21:55
  • 1
    @Bob__ Hmm, clang seems to mess it up in general when `source_location` is a default argument to function templates. https://godbolt.org/z/xvxMj3esz - Edit: It doesn't matter if it's a function template. It's borked either way: https://godbolt.org/z/sd5e46bxG - and I wouldn't care about that too much. They will have to fix that bug. - Edit2: It _is_ fixed, on trunk: https://godbolt.org/z/5jj5bcP1x – Ted Lyngmo Feb 01 '23 at 22:03

1 Answers1

1

Expanding on my comment, as requested by the OP.

Piotr Skotnicki's accepted answer to How to use source_location in a variadic template function? introduces an helper class and a deduction guide:

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

// The deduction guide forces the deduction of the pack, "separating"
// it from the default parameter.
template <typename... Ts>
debug(Ts&&...) -> debug<Ts...>;

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

Here, though, something else needs to be passed. My proposal is to inject the requirements as a template parameter before the pack.

#include <concepts>
#include <iostream>
#include <source_location>

template <typename R, typename... Ts>
struct debug
{    
  debug( R&& req      // <- A requirement that the pack ts must fulfill.
       , Ts&&... ts
       , std::source_location const& loc = std::source_location::current() )
  {
    std::cout << "line #" << loc.line() << ": "
              << ( req(ts...) ? "valid call\n" : "invalid call\n" );
  }
};

// We don't need to differentiate the first parameter of the pack, here.
template <typename... Args>
debug(Args&&...) -> debug<Args...>;

int main()
{
  enum enumVals : short { a = 0, b, c, count };
  
  // Since C++20 we can have templated lambdas. Here I don't need named parameters,
  // but I need named types to use std::same_as in the fold expression.
  constexpr auto all_enums = []<typename... Ts>(Ts&&...) {
    return ( ... and std::same_as<std::remove_cvref_t<Ts>, enumVals> );
  };

  debug(all_enums, a, b);   // -> line #31: valid call
  debug(all_enums, b, 1);   // -> line #32: invalid call
}

This is not as terse as setVals(a,b), in OP's code, but seems to work1 as intended.


1) Note that this works with gcc and clang(trunk) (as noted by Ted Lyngmo), but not with clang15: https://godbolt.org/z/zxxMTs4Kj

Bob__
  • 12,361
  • 3
  • 28
  • 42
  • Thank you, I'm learning from this. First time I see a unary left fold in the wild. I'm amazed that you can pass a closure not by argument, but by *template argument* & not only save a `std::invoke`, but also an ugly `.operator()` call ! This also elegantly transmits only types to the `source_location`-instantiating object thus completely eliding the issue of copying an argument of a type 10k x 10k matrix just to tell the user he passed the wrong one ! Brilliant. – ExpertNoob1 Feb 02 '23 at 09:50
  • Still can't move this closer to target (transparent API call), example : https://godbolt.org/z/56qzd74Gb – ExpertNoob1 Feb 02 '23 at 11:02
  • @ExpertNoob1 Yeah, sorry, I can't think of anything better. – Bob__ Feb 02 '23 at 14:09
  • Nothing to be sorry for. I don't think C++20 `source_location` can do this, honestly. Nothing wrong with `__LINE__`. I already used what I learned from you to reduce the arg checking code by 50% : my API is now single instance taking a universal `Ts...` squeezed through `(... and checkArg.operator()());` (`Is` = seq. `0 .. sizeof...(Ts)-1`) : much smaller, clearer, nicer, self contained – ExpertNoob1 Feb 03 '23 at 09:51