Search code examples
c++c++20initializer-listargument-dependent-lookupusing-declaration

Why is the function with a std::initializer_list parameter not found despite trying to bring it into consideration with a 'using' declaration


I'm trying to bring a function into consideration with a using declaration:

namespace n {
    struct S {};
    bool equal(S a, S b, std::vector<int> = {1,2}) { return false; }
}

int main() {
    using ::n::equal;
    using ::n::S;
    /// ...
}

This works well as long as no std::initializer_list is passed as the third argument:

equal(S{},S{});
equal(S{},S{},{1});

But, it breaks when a std::initializer_list is passed as the third argument:

equal(S{},S{},std::initializer_list<int> {1, 2});

It only considers std::equal.

I struggle to understand the reason why this is not working, and to find a solution to enable this pattern.

live demo


Solution

  • I'm going to number these calls:

    equal(S{},S{});                                    // #1
    equal(S{},S{},{1});                                // #2
    equal(S{},S{},std::initializer_list<int>{1, 2});   // #3
    

    So in #1 and #2, the only viable candidate is n::equal, so it gets called and everything works.

    But in #3, std::equal suddenly becomes a candidate - it's found by argument-dependent lookup (ADL) because of the explicit std::initializer_list argument. And there is an overload of std::equal that looks like this:

      template<class InputIterator1, class InputIterator2>
        constexpr bool equal(InputIterator1 first1, InputIterator1 last1,
                             InputIterator2 first2);
    

    Note that while the template parameters are named InputIterator1 and InputIterator2, and those types really should be input iterators, the algorithm itself is not constrained, so it is considered to be a viable candidate.

    And between n::equal and std::equal, the latter is a better match - all the arguments match exactly, whereas for n::equal an explicit conversion is required from std::initializer_list<int> to std::vector<int>. As such, std::equal is selected - which then won't compile because none of thse types are iterators.


    The simplest solutions are to just not use ADL, or to not pass an explicit std::initializer_list<int> and just manually pass a std::vector<int>.

    Alternatively, you could add an extra overload of n::equal to handle that case:

    bool equal(S a, S b, std::vector<int> = {1,2});
    bool equal(S a, S b, std::initializer_list<int> xs) {
        return equal(a, b, std::vector<int>(xs));
    }
    

    In constrast, std::ranges::equal is both constrained and cannot be found by ADL.