【问题标题】:Detecting a constexpr size() member function in C++ 14在 C++ 14 中检测 constexpr size() 成员函数
【发布时间】:2016-10-05 02:46:44
【问题描述】:

我有一些通用代码想知道何时传递了一系列对象,这些对象的数量在编译时是已知的,因为它可以选择一种替代算法策略。为此,我尝试编写一个has_constexpr_size(T) constexpr 函数,如下所示,它试图探测T 的size() 成员函数,看看它是否可以作为constexpr 执行。

请注意,这里与通常的“我可以检测 constexpr 执行上下文吗?”有一个关键区别。问题是因为某些 STL 容器(如 array<T> always)提供了一个 constexpr-available size() 函数,而其他 STL 容器如 initializer_list<T> 获得一个 constexpr-available size() 函数当且仅当初始化列表本身是 constexpr (因为实现取决于内部指针对,并且那些需要是 constexpr 以使 size() 具有全 constexpr 输入)。因此,has_constexpr_size(T) 函数采用一个正在测试的 T 类型的实例,以便它可以检测这两种情况。

这是一个测试用例:

#include <array>
#include <type_traits>
#include <utility>
#include <vector>

namespace type_traits
{
  namespace detail
  {
    template <size_t N> struct Char
    {
      char foo[N];
    };
    template <class T> constexpr inline Char<2> constexpr_size(T &&v) { return (v.size(), true) ? Char<2>() : throw 0; }
    template <class T> inline Char<1> constexpr_size(...) { return Char<1>(); }
  }
  //! Returns true if the instance of v has a constexpr size()
  template <class T> constexpr inline bool has_constexpr_size(T &&v)
  {
    return noexcept(detail::constexpr_size<T>(std::forward<T>(v)));
  }

  // Non-constexpr array (always has a constexpr size())
  auto ca=std::array<int, 2>();
  // Constexpr initializer_list (has constexpr size()). Note fails to compile on VS2015 as its initializer_list isn't constexpr capable yet
  constexpr std::initializer_list<int> cil{1, 2};
  // Non-constexpr initializer_list (does not have constexpr size())
  std::initializer_list<int> il{1, 2};
  // Non-constexpr vector (never has constexpr size())
  std::vector<int> vec{1, 2};

  // Passes on GCC 4.9 and clang 3.8
  static_assert(ca.size(), "non-constexpr array size constexpr");
  // Passes on GCC 4.9 and clang 3.8
  static_assert(cil.size(), "constexpr il size constexpr");
  // Fails as you'd expect everywhere with non-constexpr il error
  //static_assert(il.size(), "non-constexpr il size constexpr");

  // Passes on GCC 4.9, fails on VS2015 and clang 3.8
  static_assert(has_constexpr_size(ca), "ca");  // Should NOT fail on VS2015 and clang 3.8
  // Fails on GCC 4.9 and clang 3.8. VS2015 doesn't apply.
  static_assert(has_constexpr_size(cil), "cil");  // FAILS, and it should not!
  // Passes, correct
  static_assert(!has_constexpr_size(il), "il");
  // Passes, correct
  static_assert(!has_constexpr_size(vec), "vec");

  constexpr bool test()
  {
    return has_constexpr_size(std::initializer_list<int>{1, 2});
  }
  constexpr bool testval=test();
  // Fails on GCC 4.9 and clang 3.8. VS2015 doesn't apply.
  static_assert(testval, "test()");
}

您会注意到 array&lt;T&gt;::size() 和 constexpr initializer_list&lt;T&gt;::size() 的直接静态断言在所有编译器上都能正常工作,正如您所期望的那样。我的has_constexpr_size(T) 函数适用于array&lt;T&gt;,但仅适用于 GCC 4.9,我认为这是因为 GCC 的特别宽容的 constexpr 实现。我的 has_constexpr_size(T) 函数在所有编译器上的 constexpr initializer_list&lt;T&gt; 都失败。

所以我的问题是:

  1. 是否可以编写一个has_constexpr_size(T) 函数来正确检测array&lt;T&gt; 和constexpr initializer_list&lt;T&gt; 的constexpr 可用size() 成员函数以及提供constexpr 可用size() 函数的任何其他类型?

  2. 如果当前在 C++ 或当前编译器中无法实现 (1),是否可以编写一个 has_constexpr_size(T) 函数来正确检测 array&lt;T&gt; 的 constexpr-available size() 成员函数以及提供的任何其他类型一个始终可用的 constexpr size() 函数? 注意此解决方案不检查是否仅存在某些 size() 函数,而是检查它是否为 constexpr-available。所以has_constexpr_size(std::vector&lt;int&gt;()) 会是假的。

【问题讨论】:

    标签: c++ c++11 template-meta-programming constexpr


    【解决方案1】:

    编辑:不幸的是,如果您将std::string 替换为类型而不是int,则以下答案是错误,例如std::array&lt;std::string, 2&gt; 你会发现它失败了。出于某种对我来说没有意义的原因,C++ 14 将以下内容视为:

      static_assert(static_cast<std::array<int, 2> *>(0)->size(), "foo");          // passes
      static_assert(static_cast<std::array<std::string, 2> *>(0)->size(), "foo");  // fails
    

    我正式傻眼了。无论如何,出于历史兴趣,我将在下面留下答案。


    对于我自己的问题 2,我有一个可行的解决方案,其中使用 Expression SFINAE 我可以检测到 array&lt;&gt; 的非零 constexpr-available size() 成员函数,并正确排除 vector&lt;&gt; 的成员函数,但它得到 constexpr @ 987654329@错了。确认在 GCC 4.9 和 clang 3.8 和 VS2015 上工作:

    #include <array>
    #include <type_traits>
    #include <utility>
    #include <vector>
    
    namespace type_traits {
      namespace detail {
        template <size_t N> struct Char { char foo[N]; };
        // Overload only available if a default constructed T has a constexpr-available non-zero size()
        template <class T, size_t N = T{}.size() + 1>
        constexpr inline Char<N> constexpr_size(const T &) {
          return Char<N>();
        }
        template <class T> constexpr inline Char<1> constexpr_size(...) {
          return Char<1>();
        }
      }
      //! Returns true if the instance of v has a constexpr size()
      template <class T> constexpr inline bool has_constexpr_size(const T &v) {
        return sizeof(detail::constexpr_size<typename std::decay<T>::type>(
                   std::move(v))) > 1;
      }
    
      // Non-constexpr array (always has a constexpr size())
      auto ca = std::array<int, 2>();
      // Constexpr initializer_list (has constexpr size()). Note fails to compile on
      // VS2015 as its initializer_list isn't constexpr constructible yet
      #ifndef _MSC_VER
      constexpr std::initializer_list<int> cil{1, 2};
      #endif
      // Non-constexpr initializer_list (does not have constexpr size())
      std::initializer_list<int> il{1, 2};
      // Non-constexpr vector (never has constexpr size())
      std::vector<int> vec{1, 2};
    
      // Correct on GCC 4.9 and clang 3.8 and VS2015
      static_assert(ca.size(), "non-constexpr array size constexpr");
      // Correct on GCC 4.9 and clang 3.8.
      #ifndef _MSC_VER
      static_assert(cil.size(), "constexpr il size constexpr");
      #endif
      // Fails as you'd expect everywhere with non-constexpr il error
      // static_assert(il.size(), "non-constexpr il size constexpr");
    
      // Correct on GCC 4.9 and clang 3.8 and VS2015
      static_assert(has_constexpr_size(ca), "ca");
      // Incorrect on GCC 4.9 and clang 3.8 and VS2015
      #ifndef _MSC_VER
      static_assert(!has_constexpr_size(cil), "cil");  // INCORRECT!
      #endif
      // Correct on GCC 4.9 and clang 3.8 and VS2015
      static_assert(!has_constexpr_size(il), "il");
      // Correct on GCC 4.9 and clang 3.8 and VS2015
      static_assert(!has_constexpr_size(vec), "vec");
    
      constexpr bool test_ca() {
        return has_constexpr_size(std::array<int, 2>{1, 2});
      }
      constexpr bool testca = test_ca();
      // Correct on GCC 4.9 and clang 3.8 and VS2015
      static_assert(testca, "testca()");
    
      constexpr bool test_cil() {
        return has_constexpr_size(std::initializer_list<int>{1, 2});
      }
      constexpr bool testcil = test_cil();
      // Incorrect on GCC 4.9 and clang 3.8 and VS2015
      static_assert(!testcil, "testcil()");          // INCORRECT!
    }
    

    所以我们现在需要的只是一些过滤掉非 constexpr 输入类型的方法,这样我们就可以正确地为非 constexpr 输入返回 false。我怀疑这与stackoverflow上其他地方的未解决问题相同:(

    非常欢迎任何进一步的进步或想法。

    【讨论】:

    • 有没有衰变后remove_cv不冗余的情况?
    • 我认为衰变并不总是删除 cv 限定符。 Checking ... en.cppreference.com/w/cpp/types/decay 说 cv 仅在类型不是数组而不是函数指针时才被删除。我同意上面这可能是多余的,但我非常确定例程只看到非常量非易失性类型以避免意外。
    • @MarcGlisse 我改进了答案,删除了 remove_cv。
    【解决方案2】:

    如果我在解释这个提议:

    std::is_constant_evaluated()

    正确地,从 C++14 开始的标准措辞甚至没有阐明在编译时必须知道什么和不知道什么。而且,无论如何,这个在 C++20 中添加的“神奇的库函数”无法在语言本身中实现。

    GCC 9 的实现是:

    constexpr inline bool
    is_constant_evaluated() noexcept
    { return __builtin_is_constant_evaluated(); }
    

    这是另一种迹象(虽然不是确定的),这不能使用其他语言结构来实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-29
      • 1970-01-01
      • 2020-09-30
      • 1970-01-01
      相关资源
      最近更新 更多