std::piecewise_construct会导致ODR违规吗?

24

std::piecewise_construct是在头文件<utility>中定义的,并且由于声明为constexpr,因此具有内部链接。我想知道在头文件中使用std::piecewise_construct是否会违反ODR(One Definition Rule)。例如:

a.hpp

#include <utility>
#include <tuple>

struct point
{
    point(int x, int y)
      : x(x), y(y)
    {}

    int x, y;
};

inline std::pair<point, point> f(int x1, int y1, int x2, int y2)
{
    return {
        std::piecewise_construct,
        std::forward_as_tuple(x1, y1), std::forward_as_tuple(x2, y2)
    };
}

翻译单元 1

#include "a.hpp"

翻译单元2

#include "a.hpp"
在TU 1中的f中的std::piecewise_construct与TU 2中的f指的是不同的对象。我怀疑f违反了ODR。
N3290(可能也适用于ISO/IEC 14882:2011)在3.2/5中列出了以下ODR的例外情况:
“如果一个名字在D的所有定义中都指向具有内部或无链接的常量对象,并且对象具有相同的文字类型,并且使用常量表达式(5.19)初始化该对象,并且使用该对象的值(但不是地址),并且在D的所有定义中,该对象具有相同的值,则这个名字可以引用这个常量对象。” f几乎满足所有要求,但是“使用该对象的值(但不是地址)”对我来说似乎有歧义。虽然std::piecewise_construct_t没有状态,但调用std::pair的piecewise构造函数会涉及隐式声明的std::piecewise_construct_t的复制构造函数的调用,其参数为const std::piecewise_construct_t&。地址被“使用”了,不是吗?
我很困惑。
参考文献:http://lists.boost.org/Archives/boost/2007/06/123353.php

2
在2分钟内仍然无法理解的问题上,我会给一个+1(我想)。 - sehe
2
@Kerrek std::cout并没有被定义多次。它只是在各自的头文件中声明了。 - Johannes Schaub - litb
1
std::coutextern 并且只定义一次。 - wilhelmtell
1
该地址是否实际使用?注84表明可能不会,因为piecewise_construct_t的复制构造函数是平凡的,因此是constexpr(7.1.5/4)。 - MSalters
也许有关于空类型的特殊术语? - sp2danny
显示剩余2条评论
2个回答

6

看起来你已经在那个Boost邮件列表中找到了答案。我的看法是,这是未定义的行为,或者至少定义不够明确。

参见此 Usenet 讨论,其中讨论了同样的问题。


我也这么认为。顺便说一下,与Boost不同的是,标准库不需要是头文件。因此,“piecewise_construct”应该声明为:extern const piecewise_construct_t piecewise_construct;。我想知道为什么没有这样做。 - iorate
我建议在comp.std.c++或comp.lang.c++.moderated上提问。 - Johannes Schaub - litb
这在技术上是非法的,但实际上极其无害。 - curiousguy

0

在 ODR 下,我认为没有冲突。

未命名的命名空间与标记为内部链接(静态)的效果相同。这确实意味着每个 TU 对此类类型/函数使用自己独特的定义。

我看待占位符(::::_1 和竞争性变体)的工作方式不是通过实例化,而是通过编译时类型推断:

_1、_2 等仅仅是占位符,它们不需要真正兼容(值不需要从一个 TU 传递到另一个 TU,它们只作为类型推断参数传递,因此它们的实际类型被推断为具有当前 TU 的identity)。

换句话说:您可以通过专门化某些特征轻松定义自己的占位符,并且它们应该仍然像魅力一样工作。

namespace boost
{
    template<int I> struct is_placeholder< 
           my_funny_own_placeholder_no_ODR_involved<I> >
    {
        enum _vt { value = I };
    };
}

我想对于piecewise_construction也可以使用同样的逻辑(但我还没有仔细研究过)。


1
“问题”在于考虑两个TUs时,f的各自定义不同,因为它们不引用相同的std::piecewise_construct且使用地址而非值。因此,潜在的ODR违规是f在每个TU中可能不同。更多信息请参见Johannes链接的讨论。 - Luc Danton
@LucDanton:这是什么问题?它们只是不同未命名命名空间中的不同函数?请注意,我主要是基于链接讨论关于 _1、_2... 占位符的。稍后我会阅读有关分段函数的链接讨论... - sehe
f 不在未命名的命名空间中。如果在命名空间中,你是正确的,那就不会有问题。 - Luc Danton
@LucDanton:我明白了;在阅读了一些该线程后,似乎它特别处理将引用传递给类型实例。这确实会是个问题,因为这些类型在技术上并不相同(未命名的命名空间根据定义是唯一的)。嗯嗯,很有意思。我以后一定会继续阅读。 - sehe
2
问题并不在于类型; std::piecewise_construct 在所有 TU 中都将具有类型 std::piecewise_construct_t,就像 const int internal = 42; 总是具有类型 int 一样。但地址会不同。 - Luc Danton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接