【问题标题】:constexpr-function parameter is considered constexpr if used directly but not if used to call another constexpr-functionconstexpr-function 参数如果直接使用则被认为是 constexpr,但如果用于调用另一个 constexpr-function 则不会
【发布时间】:2014-10-17 09:24:01
【问题描述】:

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

所以我的问题本质上是“为什么会发生这种情况”,根据关于 constexpr-s 的规则。 "this" 如下。

在其中一个 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’ 

【问题讨论】:

  • 有趣。如果这是预期的标准行为,我会感到惊讶。我可以猜测这是两个编译器中的错误......?很难相信,因为它在两个编译器中都发生了相同的情况,考虑到 constexpr 是相对较新实现的,并不难相信。

标签: c++ constexpr


【解决方案1】:

sz1 和 sz2 的主要区别在于 sz1 将 obj 的地址传递给 size 成员函数,这不是常量表达式的有效结果,但可以作为中间结果操作数。 sz2 对 obj 执行左值->右值转换以传递给 size 函数,并且由于 obj 不是常量,这使得表达式非常量。

T.C. 关于隐式与显式构造函数的观点很有趣。差异的根源在于隐式平凡的复制构造函数执行按位复制,这涉及复制(非常量)填充字节,而用户提供的复制构造函数不复制任何内容。但是标准说隐式构造函数是按成员复制的,所以它们应该被同等对待。

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

【讨论】:

    【解决方案2】:

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

    此代码使用clangg++ 编译:

    #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;
    

    然后它在两者中都不编译(g++clang),即使两个构造函数所做的事情(test 是一个完全空的类)和§12.8 [class.copy] 之间绝对没有区别/p13 声明

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

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

    一个显式默认的函数只能声明为constexpr 它会被隐式声明为constexpr

    但如果第二个 noop 调用被注释掉,两个编译器(clangg++)都会编译第二个版本的代码。

    【讨论】:

    • 我想这就是为什么如果我们使用std::initializer_list而不是std::array,即使是第一个noop()调用也无法编译。我自己假设隐式默认的构造函数会是constexpr,如果可以的话,但我猜(就像你说的那样)实现仍然太年轻。然后开始报告错误。非常感谢!
    猜你喜欢
    • 2019-05-27
    • 1970-01-01
    • 2017-05-26
    • 1970-01-01
    • 1970-01-01
    • 2019-04-22
    • 1970-01-01
    • 2018-07-10
    • 2013-05-05
    相关资源
    最近更新 更多