What is the reason for providing a default value of zero for SFINAE testers?
Asked Answered
B

1

28

I noted that much of boost and libc++/libstdc++ explicitly provide a default value of zero for SFINAE in code like

// libc++ http://llvm.org/svn/llvm-project/libcxx/trunk/include/memory
namespace __has_pointer_type_imp
{
    template <class _Up> static __two __test(...);
    template <class _Up> static char __test(typename _Up::pointer* = 0);
}

template <class _Tp>
struct __has_pointer_type
    : public integral_constant<bool, sizeof(__has_pointer_type_imp::__test<_Tp>(0)) == 1>
{
};

However it confuses me as to why this would be expected when they explicitly make the call with 0. I remember hearing somewhere it was an optimization (to speed up the compiler when instantiating the template) but I don't fully understand how that would work. I looked at the standard and it has a section that briefly describes what happens with default-arguments in relation to template argument deduction.

14.8.2

At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

The last bit there sounds concerning to my question

and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

However that sounds like the opposite of an optimization if it has to do more work. Does anyone have any reasoning why that 0 must be there, it works without it, but every single example of SFINAE in libc++ at least seems to explicitly put 0 there, even though they never call the function with no arguments.

Barony answered 18/10, 2014 at 19:16 Comment(6)
I don't know the exact reason, but maybe to raise "ambiguous call error" when one does not to pass in 0 in call expression, instead of false-negative (as the ... version matches empty arguments while non-defaulted one does not)Mondrian
Isn't it related to null pointer analysis? Like maybe the default zero disables static analysis of null pointers, and this in turn avoids lots of false positives when running clang static analyzers. Might also very slightly speedup compilation for the same reason. Just guessing... You should ask on llvm mailing lists (and back here to enlighten us ;))Charlottecharlottenburg
There are all sorts of explanations - it's impossible to know, short of reading the design/implementation documentation for that library or (failing that) asking the developer. It could be something as simple as a guideline in a style guide that the developers of this library are following.Technique
Just wild guess - but maybe this is compilation time - I know we might think it should be easier to compile without default arg value -- but maybe for this very compiler it is opposite - however I doubt it is a reason.Noel
There is no SFINAE here. For crying out lound, those are two templates of overloaded non-member functions.Whispering
Agreeing there is no SFINAE here and reading the __test code, it sounds like the author wants to handle * 0 parameters (using the default), * 1 parameter (the pointer), * N parameters (variadic ...)Synthesize
U
1

There should be little functional difference between the two choices (adding = 0 as the default value or not). For me, it is more of a code style issue. In reality, both versions exist in libc++1 and boost2, as they are contributed by different developers who may have varying preferences on this matter. For a specific example, let's consider the code snippet for __has_iterator_typedefs in libc++:

template <class _Up>
static true_type
__test(__void_t<typename _Up::iterator_category> * = nullptr,
       __void_t<typename _Up::difference_type> *   = nullptr,
       __void_t<typename _Up::value_type> *        = nullptr,
       __void_t<typename _Up::reference> *         = nullptr,
       __void_t<typename _Up::pointer> *           = nullptr);

Compare the above code with another version that lacks default values:

template <class _Up>
static true_type
__test(__void_t<typename _Up::iterator_category> *,
       __void_t<typename _Up::difference_type> *,
       __void_t<typename _Up::value_type> *,
       __void_t<typename _Up::reference> *, 
       __void_t<typename _Up::pointer> *);

In my view, the former is clearer and more straightforward, making it easier to determine how many nullptrs are required to invoke __test. These test function parameters typically lack names, so adding = nullptr can slightly enhance readability. Ultimately, this decision hinges on the developer's personal preference.


1See search result of libc++ on GitHub.
2See search result of boost on GitHub.

Unduly answered 13/4 at 12:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.