26
votes

Visual Studio 2013 Preview supports a C++14 feature called (according to this page) "Transparent Operator Functors". I'm not clear on what that means. The nearest C++14 proposal I found is this, but I'm not sure if it's the same thing: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3421

I'm looking for a more clear explanation of what it is, why it's an improvement, and maybe a snippet demonstrating its use.

1
If that is the same, it lets you replace bool less<int>::operator()( int const& lhs, int const& rhs) const { return lhs<rhs; } with auto less<>::operator()(LHS&& lhs, RHS&& rhs)const->decltype( std::forward<LHS>(lhs)<std::forward<RHS>(rhs) ) { return std::forward<LHS>(lhs)<std::forward<RHS>(rhs); }, a perfect forwarding "transparent" call to <.Yakk - Adam Nevraumont
N3421 is the same thing - it was voted into C++14 without modifications at that meeting, and that's what I implemented in 2013 Preview. I thought section II, "Motivation And Scope", clearly explained the problem and the solution, and I thought the example in section VIII "Implementation" demonstrated its use. What are you confused by?Stephan T. Lavavej
The "What's New for Visual C++" page does not mention the N3421 proposal, so I figured I would ask here. I'm clear on it now. Thanks for adding it.GravityWell

1 Answers

24
votes

The transparent operator functors proposal is there as a way to have generalised functors that are located in <functional>. I personally believe the proposal itself has a very good example that would help illustrate the need for it. However I'll go ahead and try to explain it as well.

Suppose you have a function, a very basic function mind you:

template<typename T, typename U>
auto less_than(T&& t, U&& u) -> decltype(std::forward<T>(t) < std::forward<U>(u)) {
    return std::forward<T>(t) < std::forward<U>(u);
}

However you want to use this generalised function in the <algorithm> header. You have two options, to make it a struct functor:

struct MyLessThanFunctor {
    template<typename T, typename U>
    auto operator()(T&& t, U&& u) -> decltype(std::forward<T>(t) < std::forward<U>(u)){
        return std::forward<T>(t) < std::forward<U>(u);
    }
};

Or in C++14, to make a polymorphic lambda:

[](auto&& t, auto&& u) -> decltype(auto) { 
    return std::forward<decltype(t)>(t) < std::forward<decltype(u)>(u); 
}

Both are very verbose when used in an algorithm like so:

int main() {
    std::vector<int> v = {112,12,1281271,1919101,29181,412,1 };
    std::sort(std::begin(v), std::end(v), MyLessThanFunctor()); // one
    std::sort(std::begin(v), std::end(v), [](auto&& t, auto&& u) -> decltype(auto) { 
        return std::forward<decltype(t)>(t) < std::forward<decltype(u)>(u); 
    });
}

This proposal aims to make it more compact and generalised by doing this instead:

std::sort(std::begin(v), std::end(v), std::less<>());

This gives you perfect forwarding and solves issues with truncation or problems that arise from changing the container but not the underlying type appointed by the container as mentioned by the paper.

Suppose you have a non-generalised functor:

struct Functor {
    bool operator()(uint32_t a, uint32_t b) {
        return a < b;
    }
};

And you use it with your std::vector<uint32_t> and it works all fine, but you forget about your functor not being generalised and use it with your std::vector<uint64_t>. Can you see the issue that has arisen? The elements will be truncated before being compared which is probably not what the user wanted. Generalised functors solve this issue for you before they arise.