Home > Mobile >  Is there any std::replace_if equivalent which enables using lambda for the new value
Is there any std::replace_if equivalent which enables using lambda for the new value

Time:11-19

Say if I have the following class and I want to replace AA where a_ is equal to 1.

struct AA {
    AA(int a) :a_(a) {};
    int a_;
};

std::vector<AA> aa{ AA(1), AA(2), AA(4) };
std::replace_if(aa.begin(), aa.end(), [](const AA& aa1) {return aa1.a_ == 1;}, newValue);
// Here I want the newValue to be say AA(old.a_  1).
template <class _ForwardIterator, class _Predicate, class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
void
replace_if(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred, const _Tp& __new_value)
{
    for (; __first != __last;   __first)
        if (__pred(*__first))
            *__first = __new_value;
}

From the implementation in clang shown above, I don't see any way that I can use a lambda as the newValue here.

So my question is that I want to replace some value in a vector with a new value that depends on the old value, is there any way to avoid doing a find_if, then manipulate the data stored and then replace it with the new value? without rewriting my own replace_if

edit: I know that I can just reinvent the wheel by doing below:

template <class _ForwardIterator, class _Predicate, class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
void
replace_if(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred,_Tp __new_value)
{
    for (; __first != __last;   __first)
        if (__pred(*__first))
            *__first = __new_value(*first);
}

Bust just want to know if there is an existing solution in stl or boost.

CodePudding user response:

Such an algorithm does not exist because it would basically be pointless and needlessly verbose compared to any of the reasonable alternatives. You want an algorithm that takes two user-provided functions: one which takes a const& of the value type, and one which takes a & of the value type. The former returns whether the value should be modified, and the latter modifies it.

You may as well have a single functor that both tests and modifies the value. At which point, the algorithm in question is just for_each, or range-based for.

Consider the code clarity for the alternatives:

std::replace_if(aa.begin(), aa.end(),
    [](const AA& aa1) {return aa1.a_ == 1;},
    [](AA& aa1) {aa1 = AA(aa1.a_   1); });

std::for_each(aa.begin(), aa.end(),
    [](AA& aa1) {
      if(aa1.a_ == 1)
        aa1 = AA(aa1.a_   1);
    });

for(auto& aa1 : aa)
{
  if(aa1.a_ == 1)
    aa1 = AA(aa1.a_   1);
}

I would say that the 3rd one is the cleanest, followed by the second. It puts all of the user-provided code in one place. It makes it clear that the conditional and the assignment are all part of the same operation, rather than being in separate functors.

CodePudding user response:

No, std::replace_if() does not support a lambda (or any other callable type) for the newValue.

However, it does allow any type that is implicitly convertible to the iterator's value_type, including types with a conversion operator implemented. But, as you can see in the implementation you showed, there is simply no way to access the "old" value being placed so you can compute a new value from it, like you want to do. Given the implementation shown, the only possibility I see would be to have AA overload operator= to perform addition instead of assignment, which is not a good idea.

Otherwise, std::replace_if() is simply not the right tool for you.

You could implement your own function, eg:

template <class _ForwardIterator, class _CmpPredicate, class _ModPredicate>
inline void
modify_if(_ForwardIterator __first, _ForwardIterator __last, _CmpPredicate __cmp_pred, _ModPredicate __mod_pred)
{
    for (; __first != __last;   __first)
        if (__cmp_pred(*__first))
            __mod_pred(*__first);
}

modify_if(aa.begin(), aa.end(),
    [](const AA& aa1) { return aa1.a_ == 1; },
    [](AA& aa1) { aa1.a_  = 1; }
);

Otherwise, just use a std::find_if() loop instead, eg:

auto cmp = [](const AA& aa1) { return aa1.a_ == 1; };

auto iter = std::find_if(aa.begin(), aa.end(), cmp);
while (iter != aa.end())
{
    iter->a_  = 1;
    iter = std::find_if(iter 1, aa.end(), cmp);
}

Or, you could use std::transform() instead, eg:

std::transform(aa.begin(), aa.end(), aa.begin(), 
    [](const AA& aa1) {
        int a = aa1.a_;
        if (a == 1) a  = 1;
        return AA(a);
    }
);
  •  Tags:  
  • c
  • Related