6
\$\begingroup\$

I posted a C++17 version of wrapping functions for the “two-step”, on this Answer on StackOverflow.

It’s more than just handy to have a wrapper, as I was searching for good solutions on how to use begin (etc.) in a constructor initializer list, so there was no scope to add the using declaration to, and I could not push the function definition itself down into a nested namespace.

My point (in the SO post) is to follow Eric Niebler’s advice that these should be function-call objects, not functions!

I’ve refined it for general reuse within a body of code, for ambient use within a library and available from that library for other code.

I found I needed another dummy namespace wrapper — I don’t want the use std::begin; to be declared in namespace twostep, because people who use namespace twostep; into their desired scope should get only the Begin symbol it creates, not twostep::begin née std::begin as well. From my reading of the standard, it appears that using namespace twostep will declare all the names declared in twostep, not just those names introduced.

I kept the distinct (capitalized) names rather than replacing the originals, because trying to have the new symbol (found without ADL) turn around and do the lookup with ADL and the using std::begin; added in to local scope would be a horrid nightmare and a puzzle unto itself.

Unlike Eric’s rather lengthy code, I was able to use the new inline feature for variables to handle ODR; and I got SFINAE to work with the lambda function (Eric couldn’t when he wrote that), so it is rather simple now and has the makings of a good idiom worth keeping.

I wonder if it is possible to generate these lambdas? Ideally I could have

inline auto Begin = detail::wrap (&std::begin);

because the code is totally boilerplate: get the signature from the forwarded-to function, but accomplish the SFINAE as well??!

Note that I don’t really need the full power of SFINAE here: I’m not overloading on the search for Begin; I’m targeting the specific Begin symbol I want up front because it is found as a variable, not a function. So getting an error somewhere once it tries to instantiate that template is sufficient; however, I want a clean neat error (Begin has no operator()) rather than template spew. ‘neat enough’ would be OK, if a wrapper is feasible.

Here is the code with two wrappers. Once I’m certain it will be the final form, I’ll add the rest of them.

#pragma once

#include <iterator>

namespace Dlugosz::d3 {

inline namespace twostep_wrapper {
// The std:: declarations need to be in scope for the two-step functions to work,
// but we don't want them to appear as declared names in namespace twostep.  That way,
// `using namespace twostep;` will bring in Begin but not bring in begin from std.

using std::begin;
using std::end;

inline namespace twostep_inner {

inline auto Begin = [](auto&& r) ->
    decltype(begin(std::forward<decltype(r)>(r))) // using return type to do SFINAE
{
    return begin(std::forward<decltype(r)>(r));
};

inline auto End = [](auto&& r) ->
decltype(end(std::forward<decltype(r)>(r)))
{
    return end(std::forward<decltype(r)>(r));
};

} // namespace twostep_inner

} // scope wrapper

// back in namespace d3
namespace twostep = twostep_wrapper::twostep_inner;
using namespace twostep;

}
\$\endgroup\$
4
\$\begingroup\$
namespace Dlugosz::d3 {
  inline namespace twostep_wrapper {
    using std::begin;
    using std::end;

    inline namespace twostep_inner {
      inline auto Begin = [](auto&& r)
      //...

The use of inline namespaces seems inappropriate. Did you intend for the content of your inline namespaces to percolate up to d3 and pollute it? The user essentially gets access to

  • d3::begin(), (intended?)
  • d3::Begin(), (intended?)
  • d3::twostep_wrapper::begin(), (user shouldn't call this)
  • d3::twostep_wrapper::Begin(), (user shouldn't call this)
  • d3::twostep_wrapper::twostep_inner::Begin(), (user shouldn't call this)
  • d3::twostep::Begin(), (intended!)

as well as the end()/End() versions.


inline auto Begin = [](auto&& r) 
-> decltype(begin(std::forward<decltype(r)>(r)))
{
    return begin(std::forward<decltype(r)>(r));
};

C++17 provides constexpr lambdas. While lambdas may evaluate in a constexpr context implicitly, consider being explicit.

Consider inheriting the noexcept specification from the function that is selected in overload resolution.

inline constexpr auto Begin = [](auto&& r) 
noexcept(noexcept(begin(std::forward<decltype(r)>(r))))
-> decltype(begin(std::forward<decltype(r)>(r)))
{
    return begin(std::forward<decltype(r)>(r));
};

You listed Niebler's blog post, but he also has a proposal you may want to check out.

\$\endgroup\$
  • \$\begingroup\$ right, the inline is incorrect there. Sloppy editing when I figured out I needed the dummy wrapper, which is different from the normal pattern of just adding a name for importing as a group. \$\endgroup\$ – JDługosz May 2 '18 at 19:35
  • \$\begingroup\$ noexcept: yes, I should do that. So now I repeat the body 3 times! Time for a macro to stamp these out I guess. \$\endgroup\$ – JDługosz May 2 '18 at 19:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.