在C++中检测constexpr size()成员函数14

Nia*_*las 7 c++ template-meta-programming constexpr c++11

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

请注意,与通常的"我可以检测到constexpr执行上下文吗?"存在一个关键区别.问题是因为一些STL容器array<T> 总是提供constexpr-available size()函数,而其他STL容器就像initializer_list<T>获得constexpr-available size()函数一样,当且仅当初始化列表本身是constexpr时(因为实现依赖于内部指针对,那些需要是constexpr用于size()拥有all-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()");
}
Run Code Online (Sandbox Code Playgroud)

您会注意到直接静态断言array<T>::size()和constexpr initializer_list<T>::size()在所有编译器上都能正常工作,正如您所期望的那样.我的has_constexpr_size(T)功能正常array<T>,但仅限于GCC 4.9,我认为这是因为GCC特别容忍constexpr实现.我的has_constexpr_size(T)函数initializer_list<T>在所有编译器上都没有constexpr .

所以我的问题是:

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

  2. 如果(1)当前不可能在C++或当前编译器中,是否可以has_constexpr_size(T)编写一个函数来正确检测constexpr可用的size()成员函数array<T>以及提供始终constexpr可用size()函数的任何其他类型?请注意,此解决方案不会检查是否size()存在某个函数,而是检查constexpr是否可用.那has_constexpr_size(std::vector<int>())将是错误的.

Nia*_*las 0

编辑:不幸的是,下面的答案是错误的,如果您替换std::string为类型而不是例如,intstd::array<std::string, 2>会发现它失败。由于某种对我来说毫无意义的原因,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
Run Code Online (Sandbox Code Playgroud)

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


我对自己的问题 2 有一个可行的解决方案,其中使用表达式 SFINAE 我可以检测到非零 constexpr 可用size()成员函数 forarray<>并正确排除该 for vector<>,但它会导致 constexprinitializer_list<>错误。已确认在 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!
}
Run Code Online (Sandbox Code Playgroud)

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

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