问题 constexpr-function参数如果直接使用则被视为constexpr,但如果用于调用另一个constexpr函数则不被认为是constexpr


在尝试使用constexpr函数和模板(以及非类型模板参数)时,我偶然发现了一个现象,我无法理解哪个规则使其生效。

因此,根据有关constexpr-s的规则,我的问题基本上是“为什么会发生这种情况”。 “这个”如下。

在其中一个constexpr函数中,如果直接使用参数,则在编译时计算中使用此参数没有问题。 (示例第2行)

当相同的参数用作另一个constexpr函数的参数时,编译器会抱怨此表达式(参数id)不是constexpr。 (例子第3行)

简而言之:

template <typename T> constexpr std::size size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return std::make_index_sequence< obj.size() > { }.size(); } // OK ...
template <typename T> constexpr auto sz2 (T obj) { return std::make_index_sequence< size(obj) > { }.size(); } // ERROR
  // "obj" is [suddenly] not a constexpr

g ++ - 4.9.1和clang ++ - 3.4.2都会发生这种情况。

下面是一个小型测试程序,用于快速简便的实验。


#include <utility>
#include <array>
#include <iostream>

// utils
template <size_t N> using require_constexpr = std::make_index_sequence<N>;
template <typename...> constexpr void noop (void) { }

// size() wrappers
template <typename T> constexpr std::size_t size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return size(require_constexpr< obj.size() > { }); }
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }

int main0 (int, char**)
{
  constexpr auto const ar = std::array<int, 4u> { 4, 5, 6, 7 };

  // Check constexpr-computability of size(), sz1() and the expansion of sz2()
  noop<
    require_constexpr<
      size(require_constexpr< ar.size() > { }) + sz1(ar) +
      size(require_constexpr< size(ar)  > { })
    >
  >();

  // But this is an error
  // ERROR: "obj" is not a constexpr in sz2()
//noop< require_constexpr< sz2(ar) > >();

  return 0;
}

编辑 这是相对编译输出。

 src/main1.cpp:12:87: error: non-type template argument is not a constant expression
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                                                                       ^~~~~~~~~
 src/main1.cpp:28:32: note: in instantiation of function template specialization 'sz2<std::array<int, 4> >' requested here
       noop< require_constexpr< sz2(ar) > >();
                                ^
 src/main1.cpp:12:92: note: read of non-constexpr variable 'obj' is not allowed in a constant expression
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                                                                            ^
 src/main1.cpp:12:92: note: in call to 'array(obj)'
 src/main1.cpp:12:49: note: declared here
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                            ^

GCC

src/main1.cpp: In substitution of ‘template<long unsigned int N> using require_constexpr = std::make_index_sequence<N> [with long unsigned int N = size<std::array<int, 4ul> >(obj)]’:
src/main1.cpp:12:102:   required from ‘constexpr auto sz2(T) [with T = std::array<int, 4ul>]’
src/main1.cpp:28:38:   required from here
src/main1.cpp:12:102: error: ‘obj’ is not a constant expression
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                                                                                      ^
src/main1.cpp:12:102: note: in template argument for type ‘long unsigned int’ 

8679
2017-08-23 18:57


起源

有趣。如果这是预期的标准行为,我会感到惊讶。我猜可能这两个编译器都是一个错误......?难以置信,因为它在两个编译器中都发生了相同的事情,而不是很难相信考虑 constexpr 是相对新实施的。 - bolov


答案:


这看起来像两个编译器如何处理编译器生成的复制构造函数的错误。

此代码使用两者编译  和 克++

#include <utility>

// utils
template <std::size_t N> struct require_constexpr { constexpr std::size_t size() const { return N; } };
struct test { 
  constexpr std::size_t size() const { return 0; } 
  constexpr test() { }
  constexpr test(const test &) { }
};
template <typename...> constexpr void noop (void) { }

// size() wrappers
template <typename T> constexpr std::size_t size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return size(require_constexpr< obj.size() > { }); }
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }

int main (int, char**)
{
  constexpr auto const ar = test();

  // Check constexpr-computability of size(), sz1() and the expansion of sz2()
  noop<
    require_constexpr<
      size(require_constexpr< ar.size() > { }) + sz1(ar) +
      size(require_constexpr< size(ar)  > { })
    >
  >();

  noop< require_constexpr< sz2(ar) > >();

  return 0;
}

但是,如果我们改变线

constexpr test(const test &) { }

constexpr test(const test &) = default;

然后它既不编译(克++),即使两个构造者之间完全没有区别(test 是一个完全空的类),§12.8[class.copy] / p13表明

如果隐式定义的构造函数满足要求   一个 constexpr 构造函数(7.1.5),隐式定义的   构造函数是 constexpr

此外,如果隐式复制构造函数不是 constexpr,然后使用显式默认声明 constexpr 应该导致程序格式错误,需要诊断(§8.4.2[dcl.fct.def.default] / p2):

可以声明明确默认的函数 constexpr 除非   它会被隐含地声明为 constexpr

但两个编译器(克++)如果第二个编译第二个版本的代码 noop 电话被注释掉了。


5
2017-08-23 19:58



我想这就是为什么不是第一个 noop() 调用编译而不是 std::array 我们用一个 std::initializer_list。我自己假设隐含的默认构造函数会是 constexpr 如果他们可以,但我想(就像你说),实施仍然太年轻。然后关闭错误报告。非常感谢! - Prikso NAI


sz1和sz2之间的关键区别在于sz1将obj的地址传递给size成员函数,这不是常量表达式的有效结果,但可以作为中间结果操作数。 sz2对obj执行lvalue-> rvalue转换以传递给size函数,并且因为obj不是常量,所以这使得表达式不是常量。

T.C.关于隐式与显式构造函数的观点很有意思。差异的来源是隐式普通拷贝构造函数执行按位拷贝,这涉及复制(非常量)填充字节,而用户提供的拷贝构造函数不复制任何内容。但标准说隐式构造函数执行成员复制,因此它们应该被视为相同。

不清楚的是,他们是应该被拒绝还是被接受;严格阅读5.19表明两者都应该被拒绝,因为两者都涉及使用复制构造函数对obj进行左值 - >右值转换。我已经向C ++委员会提出了这个问题。


5
2017-11-19 03:16