A M
A M

Reputation: 15267

const qualifier in lambda(parameter) for std::transform with std::sregex_token_iterator

I answered a question on SO (this) to parse some string and to produce a certain output. For that I used std::transform. For the unary_op, I used a lambda. The lambda's parameter obviously needs to be const. Forcing me to introduce an additional local variable. Without using const I get a syntax error (Compiler: MS VS19).

I checked std::transorm documentation on cppreference here.

unary_op - unary operation function object that will be applied.

The signature of the function should be equivalent to the following:

Ret fun(const Type &a);

The signature does not need to have const &. The type Type must be such that an object of type InputIt can be dereferenced and then implicitly converted to Type. The type Ret must be such that an object of type OutputIt can be dereferenced and assigned a value of type Ret. ​

I do not fully understand this. Do I need to use a const& parameter or not. And is it a consequence of the implementation of the std::sregex_token_iterator? I tried to understand the documentation for std::regex_token_iterator. Sorry, I cannot understand if dereferencing this iterator can be converted to a std::string.

.

Am I on the wrong track here? Or can somebody explain me why it behaves like that? Or is there a solution which I do not see?

.

Example code:

#include <string>
#include <iostream>
#include <regex>
#include <iterator>

int main()
{
    // 1. This is the string to convert
    std::string line("Hi there buddy");

    // 2. We want to search for complete words
    std::regex word("(\\w+)");

    // 3. Transform the input string to output lines
    std::transform(
        std::sregex_token_iterator(line.begin(), line.end(), word, 1),
        std::sregex_token_iterator(),
        std::ostream_iterator<std::string>(std::cout, "\n"),
        [](const std::string & w) {    // ******* Must be   const *******
            static int i = 1; 
            std::string s = w; 
            s[0] = ::toupper(s[0]);
            return std::string("List[") + std::to_string(i++) + "]=" + s; 
        }
    );
    return 0;
}

Upvotes: 1

Views: 427

Answers (2)

Useless
Useless

Reputation: 67743

I do not fully understand this. Do I need to use a const& parameter or not

The key is that you're not supposed to mutate the value pointed at by an input iterator, only read from it. It's an input iterator, not an output iterator.

As the std::transform documentation you linked says:

unary_op and binary_op must not have side effects.

mutating the input would count as a side-effect. The operator is supposed to take the old value and return the new value, nothing else.

Do I need to use a const& parameter or not

No, but you're not allowed to mutate the original object. The documentation explicitly says

The signature does not need to have const &. The type Type must be such that an object of type InputIt can be dereferenced and then implicitly converted to Type.

So, you could take an implicit copy

[](std::string s) { /* no need to copy from w explicitly */ }

or take std::string_view (there's an implicit conversion from std::string)

[](std::string_view v) { /* ... */ }

Upvotes: 2

lubgr
lubgr

Reputation: 38287

Do I need to use a const& parameter or not?

You don't need one, but it often makes sense to have one. First, take into account that std::transform requires the unary_op to not have any side effects. This means you aren't allowed to alter the argument anyway. Next, note that the argument to unary_op is the dereferenced input iterator, as you already know. This can be an lvalue, but can also be an rvalue, depending on the input sequence you pass to std::transform. In your case, dereferencing a std::regex_token_iterator yields a std::sub_match, which has an implicit conversion operator to the underlying string type (this one)- which, again, is std::string for you. Now this is an rvalue, and an rvalue can only bind to a const-qualified reference (this is why removing const and keeping the reference doesn't compile). And there you are with another reason to put a const.

To summarize, you have these options:

  • Pass by const reference. This should be your default when using std::transform.
  • Pass by value. Do this if you want to alter a copy of the input sequence for further processing. This doesn't lead to side effects and is hence allowed.
  • If dereferencing the iterator yields a temporary, you can also pass an rvalue reference (std::string&& in your case). Modifying or move-constructing from it is legal, as this doesn't affect the std::transform input sequence, but only the temporaries.

In your case, I'd pass by value and remove the copy std::string w = v. This will construct the std::string object the lambda operates on in place (no copies).

Upvotes: 2

Related Questions