Replacement for removed bind1st in C++17

Just use std::bind:

std::bind(std::mem_fn(&CGuild::LoadGuildData), this, std::placeholders::_1)

And you can remove the superfluous std::mem_fn too:

std::bind(&CGuild::LoadGuildData, this, std::placeholders::_1)

With lambda, you might replace

std::bind1st(std::mem_fn(&CGuild::LoadGuildData), this)

by

[this](auto&& data) {return this->LoadGuildData(data);}

which give at the end something like:

DBManager::Instance().FuncQuery(
     [this](auto&& data) {return this->LoadGuildData(data);},
     "SELECT master, level, exp, name, skill_point, skill, sp, "
     "ladder_point, win, draw, loss, gold FROM guild WHERE id = %u",
     m_data.guild_id);

There are two things you could do here. And I find it really unfortunate that the current state of affairs is so awkward.


The most direct substitution is to take your bind1st and convert it exactly to bind:

std::bind(&CGuild::LoadGuildData, this, std::placeholders::_1)

Or, if you use bind semi-regularly, you probably will bring in the placeholders via using in some form or other, so this becomes:

std::bind(&CGuild::LoadGuildData, this, _1)

This is actually strictly better than bind1st since this forwards its argument, but bind1st would not.


The other thing we could do is a lambda. And here, it depends on what LoadGuildData does exactly. If it returns an object and you don't care about how this bound callable gets used necessarily, you can just write:

[this](auto const& arg){ return LoadGuildData(arg); }

This will work, most of the time. But it's not exactly the same thing as the bind expression. If LoadGuildData() returned something like an int&, the bind expression would return an int&, but this version returns an int. That might not be important. It might be not be. But if it is, you have to add, at least:

[this](auto const& arg) -> decltype(auto) { return LoadGuildData(arg); }

This will either return a reference type or not a reference type depending on what LoadGuildData actually returns.

Now... the argument, arg, might be required to be a modifiable reference, which necessitates

[this](auto&& arg) -> decltype(auto) { return LoadGuildData(arg); }

But sufficiently often, you might need something more. You might need to use this callable in a context that needs to check if it's callable. Right now, due to the rules of how this checking works - all the lambdas I've written will claim that they're callable with any argument at all. Regardless of what LoadGuildData actually takes. But if it's the wrong type, you'll get a hard compile error. Unfortunate.

So what you really need to do, to write a lambda that has the same behavior as the bind expression I wrote earlier, is:

[this](auto&& arg) -> decltype(LoadGuildData(std::forward<decltype(arg)>(arg))) {
    return LoadGuildData(std::forward<decltype(arg)>(arg)));
}

Actually, it's not quite the same behavior. This lambda is actually better in some ways - because the bind expression would not have worked if LoadGuildData were a member function template or an overload set or took a default argument - but the lambda works in all of these cases. Which is why it is so often recommend to use lambdas - they always work, they're usually the best solution, and they're sometimes the only solution.

But it's a huge mouthful, which is why so many code bases use macros. Like BOOST_HOF_RETURNS:

#define FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)
[this](auto&& arg) BOOST_HOF_RETURNS(LoadGuildData(FWD(arg)))

All of which is to say... we can't have nice things.

Tags:

C++

C++17