19

Here's my situation:

template<typename T, typename F>
inline
auto do_with(T&& rvalue, F&& f) {
    auto obj = std::make_unique<T>(std::forward<T>(rvalue));
    auto fut = f(*obj);
    return fut.then_wrapped([obj = std::move(obj)] (auto&& fut) {
        return std::move(fut);
    });
}

I want to make sure the template parameter F&& f only accepts a non-const lvalue reference. How should I enforce this?

jonspaceharper
  • 4,207
  • 2
  • 22
  • 42
Lewis Chan
  • 695
  • 6
  • 18
  • 2
    maybe a dupe: https://stackoverflow.com/questions/21573541/const-arguments-binding-to-non-const-references-in-c-templates – Rakete1111 Jun 14 '18 at 08:03
  • 2
    Utilize the [std::is_lvalue_reference](https://en.cppreference.com/w/cpp/types/is_lvalue_reference) function. – Ron Jun 14 '18 at 08:09
  • 8
    I don't get why it is downvoted so much... – YSC Jun 14 '18 at 08:16
  • 1
    If you want a specific value category for a parameter, why did you use a forwarding reference? – StoryTeller - Unslander Monica Jun 14 '18 at 08:34
  • 3
    Define the template to take that argument by reference: change `F&&` to `F&`. – Pete Becker Jun 14 '18 at 08:39
  • @YSC Me neither, voted up ... (Time passes, although not much) ... See, it's still going up, we set a trend. That is _so_ StackOverflow. – Paul Sanders Jun 14 '18 at 08:43
  • 1
    Adding to previous comments - please change `F&&` to `F&` – Daniel Heilper Jun 14 '18 at 08:56
  • @PeteBecker Please don't answer questions in comments. That's (almost) exactly what StoryTeller put in their answer. – user202729 Jun 14 '18 at 12:13
  • @YSC There are **3** comment-answers. From (relatively) high-rep users. That's a bad sign. (that the question is so trivial and/or it's just a "mental typo") – user202729 Jun 14 '18 at 12:16
  • @PaulSanders But HNQ upvote inflation... https://meta.stackexchange.com/questions/238420/prevent-questions-on-hot-list-from-being-upvoted-by-casual-visitors-only-rep-is – user202729 Jun 14 '18 at 12:19
  • @user202729 ... and they are all incomplete. – YSC Jun 14 '18 at 12:31
  • @YSC But Ron's comment is. – user202729 Jun 14 '18 at 12:35
  • @user202729 no [it is not](http://coliru.stacked-crooked.com/a/74b6f8d7b573f53d). – YSC Jun 14 '18 at 12:38
  • @YSC Fine. / But that's a possible reason. People think it's trivial so downvote it. `` – user202729 Jun 14 '18 at 12:41
  • @user202729 Yes, you're right. People _do_ seem to think something is trivial and it's all too easy to just downvote and move on. I hate that, personally, it obviously means something to the OP and I learned some new things from this thread (and if you ever read it, please ignore my previous comment, now deleted). – Paul Sanders Jun 14 '18 at 14:03

3 Answers3

36

And I want to make sure template parameter F&& f only accept a non-const lvalue reference.

Then you should not have used a forwarding reference. The whole idea of forwarding is to accept any value category and preserve it for future calls. So the first fix is to not use the wrong technique here, and accept by an lvalue reference instead:

template<typename T, typename F>
inline
auto do_with(T&& rvalue, F& f) {
  // As before
}

That should make the compiler complain nicely if you attempt to pass an rvalue into the function. It won't stop the compiler from allowing const lvalues though (F will be deduced as const F1). If you truly want to prevent that, you can add another overload:

template<typename T, typename F>
inline
void do_with(T&& , F const& ) = delete;

The parameter type of F const& will match const lvalues better (and rvalues too, btw), so this one will be picked in overload resolution, and immediately cause an error because its definition is deleted. Non-const lvalues will be routed to the function you want to define.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • I like this. The declarations set out very clearly: you can accept _all those things_ ... _except for these_. You probably also get nicer error messages if you try to use the template incorrectly. Using `static_assert` here feels a bit hacky somehow, but maybe that's just me. – Paul Sanders Jun 14 '18 at 13:37
  • @PaulSanders - There are pros and cons to all approaches. What I like about Joseph's solutions is that they are localized (the custom message is nice, and the concept solution is also very clear IMO). My mind jumped to overloading because that's how I'm used to messing with such things. But that's SO for you, everybody usually has something awesome to suggest. – StoryTeller - Unslander Monica Jun 14 '18 at 13:46
  • Yes, OK, I guess it _is_ just me, I'm quite new to all this. +1 for Joseph too then, I took a closer look and I agree with you. [Warning: philosophy alert] There are too many toys in the toybox, almost. Greenhorns (and that would include me, in most respects) can tie themselves in the most fantastic knots with all this stuff. [Erm, define 'stuff']. – Paul Sanders Jun 14 '18 at 14:00
  • 1
    @PaulSanders - As far as C++ is concerned I consider myself a greenhorn as well. I'm always learning something new about it. Time and time again someone on SO will post an elegant solution to something I considered impossible to solve cleanly. [Here's one for you](https://stackoverflow.com/a/47453921/817643). It leverages the most vexing parse itself. – StoryTeller - Unslander Monica Jun 14 '18 at 14:05
  • Yes, borderline genius. I'd have gone with the script myself, faced with that problem, that would have been my limit. I see it made a big impression on you - and no wonder. – Paul Sanders Jun 14 '18 at 14:17
12

You can take f by lvalue reference and prevent non-const values with static_assert and is_const:

template<typename T, typename F>
inline
auto do_with(T&& rvalue, F& f) {
    static_assert(!std::is_const<F>::value, "F cannot be const");
    …
}

With the introduction of constraints in C++20, you will be able to use a requires clause instead:

template<typename T, typename F>
inline
auto do_with(T&& rvalue, F& f) requires !std::is_const_v<F> {
    …
}
Joseph Thomson
  • 9,888
  • 1
  • 34
  • 38
4

to add yet another solution

template<typename T, typename F>
inline
auto do_with(T&& rvalue, F&& f) {
    static_assert(!std::is_const<typename std::remove_reference<F>::type>::value, "must be non-const");
    static_assert(std::is_lvalue_reference<F>::value, "must be lvalue reference");
    ...
}

or with SFINAE

template<typename T, typename F, typename std::enable_if<!std::is_const<typename std::remove_reference<F>::type>::value && std::is_lvalue_reference<F>::value, int>::type = 0>
inline
auto do_with(T&& rvalue, F&& f) {

}
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
Tyker
  • 2,971
  • 9
  • 21