C++ std::transform 副作用

发布于 2024-07-16 21:12:54 字数 895 浏览 2 评论 0原文

我已经实现了这样的 UnaryOperation

struct Converter
{
    Converter( std::size_t value ):
        value_( value ), i_( 0 )
    {}
    std::string operator() ( const std::string& word )
    {
        return ( value_ & ( 1 << i_++ ) ) ?
            word:
            std::string( word.size(), ' ' );
    }
    std::size_t value_;
    std::size_t i_;
};

我像这样使用它

std::vector v;
// initialization of v  
std::transform( v.begin(), v.end(),
                std::back_inserter( result ),
                Converter( data ) );

我的问题是我可以依赖我的假设,即算法将按照“Converter::i_”的顺序调用我的“Converter operator ()”将对应于“v”中的元素数量。

请引用标准,以防我不能依赖订单或提出类似 stl 的解决方案,以避免可能出现的问题(如果有)。

谢谢。

编辑:

我知道变换算法标准中的“无副作用”要求。 我找不到同一标准中函子的确切“副作用”。

也许有一些看起来不错的类似Boost的解决方案来完成这个任务?

I've implementation of UnaryOperation like this

struct Converter
{
    Converter( std::size_t value ):
        value_( value ), i_( 0 )
    {}
    std::string operator() ( const std::string& word )
    {
        return ( value_ & ( 1 << i_++ ) ) ?
            word:
            std::string( word.size(), ' ' );
    }
    std::size_t value_;
    std::size_t i_;
};

And I use it like

std::vector v;
// initialization of v  
std::transform( v.begin(), v.end(),
                std::back_inserter( result ),
                Converter( data ) );

My question is can I rely on my assumption that algorithm will call my 'Converter operator ()' in the order that 'Converter::i_' will correspond to number of element in 'v'.

Please quote the standard in case I can't rely on the order or put the stl-like solution that avoid possible problem if any.

Thanks.

Edit:

I am aware of "no Side effect" requirements in the standard for the transform algorithm. I can't find what is exactly "side effect" for functors in the same standard.

Maybe there is some good-looking-boost-like solution for this task?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

你是暖光i 2024-07-23 21:12:54

从标准来看:

25.2.3 变换 [lib.alg.transform]
需要:
op 和 binary_op 不应有任何副作用。

副作用(维基百科定义)

在您的情况下,我们有下一个副作用:

Converter c( data );  
c( some_const_value ) != c( some_const_value );

您不'对你的算法没有任何保证,但我相信它适用于几乎所有的 stl 实现。

建议的解决方案
看来我知道一种方法可以满足您的需求:
使用 boost::counting_iterator - 用于迭代两个容器;

它看起来像:

bool bit_enabled( size_t data, unsigned char number )
{
    return ( data & 1 << number ) != 0;
}

std::string select_word( 
                const std::string& word,
                size_t data, 
                size_t number )
{
    return bit_enabled( data, number ) ? word : std::string( ' ', word.length() );
}

const size_t data = 7;
const boost::array< std::string, 3 > vocabulary = { "a", "b", "c" };
std::vector< std::string > result;
std::transform(
    vocabulary.begin(),
    vocabulary.end(),
    boost::counting_iterator< size_t >(0),
    back_inserter( result ),
    boost::bind( &select_word, _1, data, _2 )
);

另外,如果您将定义位迭代器或将使用一些位容器,您将可以使用 boost::zip_iterator 来迭代这两个容器。

编辑:
昨天我发现感兴趣的文章其中包含标准的副作用定义。

该标准将副作用定义为
如下:访问对象
由易失性左值指定,
修改对象,调用库
I/O 函数,或调用函数
这些操作中的任何一个都是
所有副作用,即变化
执行状态
环境。

编辑:
我希望这是最新的编辑。
我一直认为“没有副作用”的意思是:
f(a) 应始终等于 f(a)。 ( f 独立于执行环境:内存/cpu/全局变量/成员变量,如您的情况等)。
“不产生副作用”的意思是——不改变执行环境。

但在 C++ 标准中,我们对副作用有更底层的定义。

将您在示例中所做的事情命名为 Stateful 函子。
标准没有提到“Statefull”函子,也没有提到函子的副本数量 - 你不能使用这个技巧,因为它是未指定的行为。

请参阅标准库问题列表(谓词的类似问题):
http://anubis.dkuug.dk/jtc1/sc22 /wg21/docs/lwg-active.html#92

Qute from standard:

25.2.3 Transform [lib.alg.transform]
Requires:
op and binary_op shall not have any side effects.

Side Effect ( wikipedia definition )

In your case we have next side effect:

Converter c( data );  
c( some_const_value ) != c( some_const_value );

You don't have any guarantees for your algorithms, but I belive that it will works on almost all stl implementations.

Suggested solution
It seems I know one way to do what you need:
use boost::counting_iterator - for iterate over two containers;

it will looks like:

bool bit_enabled( size_t data, unsigned char number )
{
    return ( data & 1 << number ) != 0;
}

std::string select_word( 
                const std::string& word,
                size_t data, 
                size_t number )
{
    return bit_enabled( data, number ) ? word : std::string( ' ', word.length() );
}

const size_t data = 7;
const boost::array< std::string, 3 > vocabulary = { "a", "b", "c" };
std::vector< std::string > result;
std::transform(
    vocabulary.begin(),
    vocabulary.end(),
    boost::counting_iterator< size_t >(0),
    back_inserter( result ),
    boost::bind( &select_word, _1, data, _2 )
);

Also maybe if you will define bit iterator or will use some bit container you will can use boost::zip_iterator for iterate both containers.

EDIT:
Yestarday I found interest article which contain definition of Side Effect by standard.

The Standard defines a side effect as
follows: Accessing an object
designated by a volatile lvalue,
modifying an object, calling a library
I/O function, or calling a function
that does any of those operations are
all side effects, which are changes in
the state of the execution
environment.

EDIT:
I hope it will be latest edit.
I am always tought that "no have side effect" mean:
f(a) should be equal f(a) always. ( f independed from execution environment: memory/cpu/global variables/member variables as in your case etc).
"Not produce side effect" mean - don't changing execution environment.

But in c++ standard we have more low-level defintion for Side effect.

Thing what you do in your example named as Stateful functor.
Standard doesn't say about "Statefull" functors, but also doesn't say about count of copies of your functor - you couldn't use this trick because it is unspecified behavior.

See Standard Library Issues list ( similar issue for predicat ):
http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/lwg-active.html#92

芯好空 2024-07-23 21:12:54

我刚刚检查了标准,如果我理解正确的话,答案是否定的。 “transform”的描述有以下附加要求(25.2.3):

要求:op和binary_op不应有任何副作用。

回到我的记忆深处,我记得 Nicolai JosuttisACCU 会议,他在会上展示了对于特定类型的容器和 STL 实现,函数对象被复制。 埃里克提供了链接指向 Dobb 博士的文章,该文章更详细地讨论了这些差异。

编辑:替代解决方案:

for_each算法没有此限制,因此您可以更改 Converter 对象以引用结果向量并在 Converter 内执行push_back功能。

struct Converter
{
  Converter( std::size_t value, std::vector<std::string> & result ):
      value_( value ), i_( 0 ), result_(result)
  {}
  void operator() ( const std::string& word )
  {
    result_.push_back ( value_ & ( 1 << i_++ ) ) ?
             word:
             std::string( word.size(), ' ' );
  }
  std::size_t value_;
  std::size_t i_;
  std::vector<std::string> & result_;
};

并使用for_each而不是transform

std::vector v;
// initialization of v  
std::for_each ( v.begin()
              , v.end(),
              Converter( data, result ) );

I've just checked the standard and if I understand it correctly the answer is no. The description of 'transform' has the following additional requirement (25.2.3):

Requires: op and binary_op shall not have any side effects.

Reaching back into the depths of my memory, I remember a talk given Nicolai Josuttis at an ACCU conference, where he showed that for a particular type of container and STL implementation, the function object was copied. Éric provided this link to a Dr. Dobb's article that discusses the differences in more detail.

EDIT: Alternative solution:

The for_each algorithm does not have this restriction, so you could change your Converter object to take a reference to the result vector and do the push_back inside the Converter function.

struct Converter
{
  Converter( std::size_t value, std::vector<std::string> & result ):
      value_( value ), i_( 0 ), result_(result)
  {}
  void operator() ( const std::string& word )
  {
    result_.push_back ( value_ & ( 1 << i_++ ) ) ?
             word:
             std::string( word.size(), ' ' );
  }
  std::size_t value_;
  std::size_t i_;
  std::vector<std::string> & result_;
};

And use for_each rather than transform:

std::vector v;
// initialization of v  
std::for_each ( v.begin()
              , v.end(),
              Converter( data, result ) );
人海汹涌 2024-07-23 21:12:54

作为副作用肯定是一件坏事的例子,请考虑一个假设的并行 STL 实现,它将工作分配给多个 CPU。

我相信这是STL的作者们的想法。 最初的 STL 来自 SGI,它是构建大规模并行单图像和集群系统的知名公司之一。

As an example of a case where side-effects would be a definite bad thing, consider a hypothetical parallel STL implementation which split the work between several CPUs.

I believe this was in the minds of the authors of STL. The original STL was from SGI, one of the larger names in building massively parallel single-image and cluster systems.

—━☆沉默づ 2024-07-23 21:12:54

是和不是。 这是因为您将迭代器提供给 Converter 对象。 对于像 vector 这样的序列容器,您可以得到 i 来对应元素的顺序。 但是,对于像 map/multimap/set/multiset 这样的关联容器,这可能无效(很可能无效)。

Yes and no. This is because you feed iterators to your Converter object. For a sequence container like vector you get i to correspond to the order of the elements. However, for associative containers like map/multimap/set/multiset this may not be (will most probably not be) valid.

£冰雨忧蓝° 2024-07-23 21:12:54

我相信在某些情况下答案是肯定的,但也有明确的情况是否定的。 如果无法回忆起哪个(抱歉),并且为了使代码不那么脆弱,我不会依赖它并将 i 保留在仿函数之外以确保安全。
即在 Converter 中执行以下操作:

<代码>
转换器( std::size_t value, std::size_t& i ): value_( value ), i_ ( i ) {}


std::size_t &i_;

然后调用

std::向量 v;
std::size_t i(0);
// v 的初始化
std::transform( v.begin(), v.end(),
std::back_inserter( result ), Converter( data, i ) );

凌乱,但更安全。

I believe there are some instances where the answer is yes, but there are also definite cases where it's no. Without being able to recall which (sorry), and to make the code less brittle, I would not rely on it and keep i outside the functor to be safe.
ie inside Converter do:


Converter( std::size_t value, std::size_t& i ): value_( value ), i_ ( i ) {}


and
std::size_t &i_;

then invoke with

std::vector v;
std::size_t i(0);
// initialization of v
std::transform( v.begin(), v.end(),
std::back_inserter( result ), Converter( data, i ) );

Messy, but safer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文