1

I have a class with an in-class defined friend function that I would ideally not modify (it comes from a header that's already deployed)

#include <numeric>
#include <vector>

namespace our_namespace {
template <typename T>
struct our_container {

  friend our_container set_union(our_container const &, our_container const &) {
    return our_container{};
  }
};
}  // namespace our_namespace

set_union is not declared outside the struct or namespace, but can normally be found through argument-dependent lookup (c.f. Access friend function defined in class). However, I ran into a situation where I need the function unevaluated (i.e. without arguments to be evaluated) for template type deduction. More specifically, I would like to use the friend function as a binary operation in std::accumulate:

auto foo(std::vector<our_namespace::our_container<float>> in) {      
  // what I really wanted to do:
  return std::accumulate(std::next(in.begin()), in.end(), *in.begin(),
                       set_union);
}

The only workaround I found so far is to wrap the function call into a lambda:

auto foo(std::vector<our_namespace::our_container<float>> in) {    
  // what I ended up doing:
  return std::accumulate(std::next(in.begin()), in.end(), in[0],
                       [](our_namespace::our_container<float> const& a, our_namespace::our_container<float> const& b) {return set_union(a,b);});
}

(and of cause I could define a function that does the same as the lambda).

I've tried:

  • specifying the namespace for set_union (what I usually do to get around ADL, but set_union isn't in a namespace)
  • spell out template arguments to set_union<our_container> (but set_union lookup isn't failing at template argument deduction of set_union, and isn't templated itself)
  • spell out the type of set_union with …,decltype(set_union) set_union); … except, here the lookup of set_union fails for the same reason, and providing arguments to set_union in the decltype would just trigger its evaluation and lead to the return type of set_union rather than its type.

Is there another way to use accumulate with ADL than this lambda?

grooveplex
  • 2,492
  • 4
  • 28
  • 30
pseyfert
  • 3,263
  • 3
  • 21
  • 47
  • 3
    I'm pretty sure the answer you are going to get is no. That said, your lambda can be shortened to `[](auto const& a, auto const& b) {return set_union(a,b);}` – NathanOliver Feb 07 '19 at 19:08
  • @NathanOliver wow. I wouldn't have thought that could work since the signature of the lambda is now unspecified when the template arguments needs to be deduced and that's before the lambda gets called to deduce it's argument types … but indeed you're right, it works (and the type of the lambda is fine with autos in it `foo(std::vector >)::` ) – pseyfert Feb 07 '19 at 19:19
  • 1
    It works because the type of the function is not deduced. a lambda expression generate an object that has an overloaded `operator()` (in this case it will be a template function). It is the type of the object that gets deduced by `accumulate`, not the `operator()` call. This is part of the reason functors/lambdas are so useful. – NathanOliver Feb 07 '19 at 19:21
  • 1
    Actually you don't need the `const`s in the lambda either, `auto` will deduce `T const` instead of `T`: `[](auto &a, auto &b) {return set_union(a,b);}` – N00byEdge Feb 07 '19 at 19:28

1 Answers1

3

Using a lambda looks like the best way to me.

As @NathanOliver suggested, you could shorten it to:

[](auto const& a, auto const& b) {return set_union(a,b);}

If you insist, there is an alternative, but it's somewhat ugly.

specifying the namespace for set_union (what I usually do to get around ADL, but set_union isn't in a namespace)

It is in the namespace, but it can only be found with ADL.

If you redeclare it outside of the class, you'll be able to find it with the regular lookup:

namespace our_namespace
{
    our_container<float> set_union(our_container<float> const &, our_container<float> const &);
}

auto foo(std::vector<our_namespace::our_container<float>> in)
{
    // what I really wanted to do:
    return std::accumulate(std::next(in.begin()), in.end(), *in.begin(),
                           our_namespace::set_union<float>);
}

Unfortunately, you can't declare it as a template (since it's not a template).

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207