3

Please consider the following little function. It provides compiler abstraction for breaking the debugger programmatically:

inline constexpr void BreakDebug()
{
   #ifdef __GNUC__
      __builtin_trap();
   #elif defined _MSC_VER
      __debugbreak();
   #endif
}

I would like to rewrite the function and replace the preprocessor instructions using C++20 code. Since __builtin_trap and __debugbreak are compiler specific and mutual exclusive, I can't use a simple if constexpr since I would get a compilation error.

Assuming I would wrap the compiler macros __GNUC__ and _MSC_VER using a constexpr enumeration constant... how could that be done?

Silicomancer
  • 8,604
  • 10
  • 63
  • 130
  • 1
    would you get a compilation error? Does `if constexpr(true) ... else` really complain about the `else`? – 463035818_is_not_an_ai Jun 08 '20 at 09:39
  • It either complains about missing `__builtin_trap` or missing `__debugbreak`. – Silicomancer Jun 08 '20 at 09:41
  • 1
    Why not use [`std::abort`](https://en.cppreference.com/w/cpp/utility/program/abort)? – YSC Jun 08 '20 at 09:43
  • 1
    @idclev463035818 `if constexpr` does its thing only in templates, when the condition depends on a template parameter (and even then, both branches have to be valid [for some template arguments](http://eel.is/c++draft/temp#res-8.1)). Otherwise it works like a regular `if`. – HolyBlackCat Jun 08 '20 at 09:56
  • 3
    @YSC I guess OP wants to continue execution after hitting the breakpoint. – HolyBlackCat Jun 08 '20 at 09:57
  • @HolyBlackCat yes, I had to convince myself. Then first idea was to go back to good old function template specialization, but thats of course the same. Thanks a lot for the quote. I was looking for it recently and didnt find it :) – 463035818_is_not_an_ai Jun 08 '20 at 09:58

2 Answers2

4

You only need a declaration of the function that is not called (and no definition, as it won't be called):

inline constexpr void BreakDebug()
{
   #ifdef __GNUC__
   constexpr bool GNU = true; 
   constexpr void __debugbreak();
   #elif defined _MSC_VER
   constexpr bool GNU = false;  
   constexpr void __builtin_trap();
   #endif


   if constexpr (GNU){
      __builtin_trap();
   } else {
      __debugbreak();
   }
}

By declaring only that function that does not exist and that you will never call, you can avoid linker errors in case the signature is not the correct one.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Looks like a feasible solution. Still it feels unclean to repeat external function declarations. I hoped there would be a cleaner (meta programming) solution. – Silicomancer Jun 08 '20 at 10:09
  • @Silicomancer You need a declaration so you provide one. I don't understand why you call that "unclean". Btw if you follow the "alternative" then there is no repetition – 463035818_is_not_an_ai Jun 08 '20 at 10:23
  • @Silicomancer related: https://stackoverflow.com/q/8814705/4117728 – 463035818_is_not_an_ai Jun 08 '20 at 10:25
  • Well, when not calling built-in compiler functions but normal functions it could happen that their prototypes change. Or I could forget to include the correct header. In both cases I would get a linker error instead of a compiler error which is not a good thing. – Silicomancer Jun 08 '20 at 10:27
  • @Silicomancer sfinae might get you somewhere, but it will be much more verbose and complicated than this (see here: https://stackoverflow.com/q/257288/4117728) – 463035818_is_not_an_ai Jun 08 '20 at 10:28
  • @Silicomancer no you would not. I think you didn't understand my "alternative". You only have to declare the function that does not exist and that you will never call, so there is no way to get a linker error – 463035818_is_not_an_ai Jun 08 '20 at 10:28
4

Apparently, those builtin functions can also be turned into dependent name. I checked this here. So the usual trick that consists in making potentially ill-formed code dependent works:

enum compiler_t{
    gnu,
    msvc
    };

inline constexpr compiler_t compiler = 
#ifdef __GNUC__
  compiler_t:: gnu;
#else
  compiler_t:: msvc;
#endif

template <class...Args>
inline constexpr void BreakDebug(Args...args)
{

   if constexpr (compiler == gnu){
      __builtin_trap(args...);
   } else {
      __debugbreak(args...);
   }
}

int main(){
   BreakDebug();
   return 0;
}
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • Could you elaborate a bit? I do not completely understand why that parameter pack trick is necessary and how it works. – Silicomancer Jun 08 '20 at 19:54
  • 2
    @Silicomancer Without the `args...` argument, the compiler can perform name lookup for __debugbreak or __builtin_trap at the point of definition of BreakDebug. This would be performed in the 2 branches of the ìf constexpr` statement and the compiler could find there that one of those name is not defined. With the `args...` argument, the compiler must know what is the type of the arguments to perfom ADL (argument dependent name look up), so it is always performed at the point of instantiation. But at the point of instantiation, the "wrong" if constexp branch is discared, so there is no error. – Oliv Jun 08 '20 at 20:11