3

I'm trying to implement a compact way of detecting if a free function is available at compile time (I'm using std::max as an example). I came up with this:

#include <stdio.h>  
#include <algorithm>           // (1)

namespace std { struct max; }  // (2)

template<typename A>
concept bool have_std_max = requires(A const& a1, A const& a2) {
  { std::max(a1, a2) }
};

template <typename A> 
constexpr A const &my_max(A const &a1, A const &a2) {
  if constexpr(have_std_max<A>) {
    return std::max(a1, a2);
  }
  else {
    return (a1 > a2) ? a1 : a2;
  }
}

int main() {
  int x = 5, y = 6;
  return my_max(x, y);
}

If I comment out (1), the detection works and my code uses the constexpr else branch (see in Compiler Explorer). However, if I comment out both (1) and (2), this code will fail to compile, because the name std::max is unknown to the compiler. Shouldn't the concept simply return false in this case? Is there a way to implement something similar without having to declare a dummy max?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Tamás Szelei
  • 23,169
  • 18
  • 105
  • 180
  • You're using a qualified name `std::max`.. I think that qualification is probably the problem! Because it shows an absence of *name* in the *said* namespace.... success of such a call doesn't depend on the template argument `A` , so the compiler can ensure *fail* without even knowing `A`. However, an unqualified name might be *searched* through ADL, and the compiler has to postpone the decision to the later stage when `A` is known. Well that is just my guess, with some homemade reasoning. – Nawaz Mar 07 '17 at 11:01
  • ... so even in the unqualified-name case, if the name is not found, then I dont know whether it should *fail* or result in `false` value for the concept. – Nawaz Mar 07 '17 at 11:08
  • 1
    @Nawaz right, I tried with an unqualified name and it indeed works without a prior declaration. – Tamás Szelei Mar 07 '17 at 11:52
  • That is great. So it behaves in the same way as expression-sfinae works! That *actually* makes sense. – Nawaz Mar 07 '17 at 12:05
  • 1
    @Nawaz sure, but this way it's less useful. Unless there is a way to do it still, or it's only an implementation error in the current gcc. – Tamás Szelei Mar 07 '17 at 12:11
  • Well, actually it makes sense, because when you write a qualified name `std::max` which doesn't exist, then I think compiler is right in shouting at you as it knows it can never succeed, no matter what you pass as arguments to the function. Also, I don't see why no-shouting could be more useful than this. – Nawaz Mar 07 '17 at 12:15
  • 1
    @Nawaz the above example demonstrates a valid use-case where no-shouting is useful. "If std::max exists, use it, if not, provide a custom implementation". Sure, std::max might not be the best example as it's very likely to exist, but think in terms of checking any arbitrary function. – Tamás Szelei Mar 07 '17 at 12:19
  • For really any *arbitrary* function, you need arbitrary *type* or/and arbitrary *namespace*. No? If one of them is known, in this case *namespace*, then you're actually committing/conforming to *a real implementation* (as opposed to *arbitrary* implementation). If that is the case, you must know what that implementation actually provides you... and what it does *not*. – Nawaz Mar 07 '17 at 12:28
  • .... Also, if you *claim* that the function should be *max* in the *std* namespace, then you must know whether it exists or not. There is nothing that you cannot do, when writing the code in the first place. The compiler doesn't gather *more* information than you have done that yourself. There is nothing *more* for the compiler to *gather and compute*. It is going to **fail always**, like I said before. – Nawaz Mar 07 '17 at 12:29
  • 1
    @Nawaz sorry, I think that derails the question. I want to check if "foo::bar()" exists, and if it does, call it, if not, provide an implementation. That's all. I don't know in advance if `bar` exists in the `foo` namespace. That's precisely what the code should detect. I expect that it might exist in that specific namespace, but I don't know in advance. – Tamás Szelei Mar 07 '17 at 12:35
  • If you don't know about its existence, then you don't the semantic also. How do you use things whose semantics you don't clearly know? If namespace could be *passed* as some kind of template parameter `T`, then what you said could be a part of *concepts*, but then `namespace` is not a part of concepts.. you cannot pass *namespace* as template argument. – Nawaz Mar 07 '17 at 12:38
  • 1
    @Nawaz If the concept check was successful I know that I can use the function to form an expression like it's specified in the concept. In other words, I'm checking because I expect a certain function with certain semantics to be there; if it's not there, then I provide a custom implementation. Just like in my working example code. – Tamás Szelei Mar 07 '17 at 12:43
  • I don't see any advantage. It doesn't increase the degree of genericity, because the *namespace* is *known* in advance at the time of writing the code. Expecting an unknown thing to exist in a known namespace, is akin to expecting 4 to be equal to 5, or so, and writing code like `if(4==5) {....} else { cout << "Hello World"; }`, and saying this is more useful. Fine, you can write this, but the `if` block would **never** be executed. That is the point which does *not* increase its usefulness! – Nawaz Mar 07 '17 at 12:51
  • 1
    @Nawaz no it's not? Such code can prepare for cases when the standard library implementation is incomplete for example. At this point I can't tell if you are trolling or not, so I'll stop answering, sorry. – Tamás Szelei Mar 07 '17 at 12:54
  • "*the standard library implementation is incomplete*" is the only valid argument it seems. But then C++ handles this differently, you need to check these: [feature testing macros](http://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros) and [more testing](http://en.cppreference.com/w/cpp/experimental/feature_test#Finding_Headers). As for concepts doing the same, well, the purpose of concepts is not to detect "incompleteness of library", rather enhancing "generic programming". Also, macro-based solution works for *core* language and library both, not just the library! – Nawaz Mar 07 '17 at 13:05
  • Now. I'm going to stop commenting here, as it seems "trolling" to you! – Nawaz Mar 07 '17 at 13:05

1 Answers1

2

The template system of C++ is never an excuse to write incorrect code. Templates only offer leeway to so-called dependent types and expressions. (A long topic of its own which is studied in more depth in this answer.) For our purposes, a qualified name of the form std::max does not involve anything dependent so it must be correct where it appears. In turn, that means the look-up must succeed.

You were on the right track by trying to add a max declaration. By doing this, the non-dependent qualified name always successfully finds a declaration. Meanwhile, the overall expression remains dependent (by virtue of involving a0 and a1). All that remains is to avoid polluting the std namespace, which we aren't allowed to do:

#include <algorithm>

namespace dirty_tricks {

namespace max_fallback {

// non-deducible on purpose
template<typename Dummy>
void max() = delete;

} // max_fallback

namespace lookup {

// order of directives is not significant
using namespace std;
using namespace max_fallback;

} // lookup

} // dirty_tricks

template<typename Val>
concept bool have_std_max = requires(Val arg) {
    // N.B. qualified call to avoid ADL
    dirty_tricks::lookup::max(arg, arg);
};

(When testing the code by removing the <algorithm> include make sure the std namespace is still declared, or the using directive might fail. As seen in this Coliru demo.)

Now dirty_tricks::lookup::max either finds both std::max and dirty_tricks::max_fallback::max or the latter alone, but it can't fail. We also make sure that our own max overload cannot result in a valid expression by deleting it (and otherwise valid calls would look very different anyway).

Community
  • 1
  • 1
Luc Danton
  • 34,649
  • 6
  • 70
  • 114