3

考虑这个简单的 SFINAE 测试来确定一个类型是否可以作为参数std::begin

#include <utility>

    template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ()) 
{ return true; }

    template <class> constexpr bool
std_begin_callable (...) 
{ return false; }

#include <array>

static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");

int main () {}

请注意,定义特array化 for 的标头包含SFINAE 函数之后。断言失败。现在,如果我移动之前,它的工作原理。(gcc 4.8.0 20130411,clang 版本 3.2)std::begin#include <array>

我不明白为什么。作为模板的 SFINAE 函数,在包含定义他们测试的函数的标头之后,它们不应该在需要时在静态断言中实例化吗?

问题是我的 SFINAE 在标头中,我必须确保它包含任何其他容器标头之后(此问题未专门链接到array标头)。

4

1 回答 1

3

正如Xeo所说,要使其工作,您必须#include <iterator>引入适当的定义begin。更准确地说,这有效:

#include <iterator>
#include <utility>

template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ()) 
{ return true; }

template <class> constexpr bool
std_begin_callable (...) 
{ return false; }

#include <array>

static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");

int main () {}

现在,让我们看看为什么不包含<iterator>编译但没有给出预期结果的原始代码(除非你向上移动#include <array>)。

包含<utility>间接意味着包含<initializer_list>which 定义std::begin(std::initializer_list<T>)。因此,在此翻译单元中,名称std::begin是可见的。

但是,当您调用std_begin_callable第一个重载时,SFINAEd 消失了,因为可见std::begin不能采用std::array.

现在,如果您完全删除<iterator>and的包含<utility>(保留<array>在 之后std_begin_callable),那么编译将失败,因为编译器将不再看到std::beginor的任何重载std::declval

template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ()) 
{ return true; } // error: begin/declval is not a member of std

template <class> constexpr bool
std_begin_callable (...) 
{ return false; }

#include <array>

static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");

int main () {}

最后,您可以使用以下方法复制/简化以前的错误行为:

namespace std {

  void begin();

  template <typename T>
  T&& declval();

}

template <class T> constexpr auto
std_begin_callable (T const*) -> decltype (std::begin (std::declval <T>()), bool ()) 
{ return true; } // No compiler error here, just SFINAE.

template <class> constexpr bool
std_begin_callable (...) 
{ return false; }

#include <array>

static_assert (std_begin_callable <std::array <int, 3>> (0), "failed");

int main () {}

更新:

从评论(此处和 OP 中)中,我想不可能以您想要的方式解决头文件顺序问题。那么,让我建议一种基于 ADL 的解决方法,它接近于解决方案,并且可能(但可能不是)对您的用例来说足够好:

// <your_header_file>
#include <iterator>
#include <utility>

namespace detail {

    using std::begin;

    template <typename T, typename = decltype(begin(*((T*)0)))>
    constexpr std::true_type std_begin_callable(int) { return std::true_type(); }

    template <typename>
    constexpr std::false_type std_begin_callable(long) { return std::false_type(); };

};

template <typename T>
constexpr auto std_begin_callable() ->
  decltype(detail::std_begin_callable<typename std::remove_reference<T>::type>(0)) {
  return detail::std_begin_callable<typename std::remove_reference<T>::type>(0);
}
// </your_header_file>   

// <a_supposedly_std_header_file>
namespace std {
    struct foo { int begin() /* const */; }; 
    struct bar;
    int begin(/*const*/ bar&);

    template <typename T> struct goo;

    template <typename T>
    int begin(/*const*/ goo<T>&);
}
// </a_supposedly_std_header_file>

// <a_3rd_party_header_file>
namespace ns {

    struct foo { int begin() /*const*/; };
    struct bar;
    int begin(/*const*/ bar&);

    template <typename T> struct goo;

    template <typename T>
    int begin(/*const*/ goo<T>&);

}
// </a_3rd_party_header_file>

//<some_tests>
static_assert ( std_begin_callable</*const*/ std::foo>(), "failed");
static_assert ( std_begin_callable</*const*/ std::bar>(), "failed");
static_assert ( std_begin_callable</*const*/ std::goo<int>>(), "failed");

static_assert ( std_begin_callable</*const*/ ns::foo>(), "failed");
static_assert ( std_begin_callable</*const*/ ns::bar>(), "failed");
static_assert ( std_begin_callable</*const*/ ns::goo<int>>(), "failed");
//</some_tests>

int main () {}

它似乎有效,但我还没有完全测试。我建议您在代码中尝试使用/不使用注释掉const的几种组合。

我使用*((T*)0)而不是std::declval<T>()因为一个常量问题。要看到它,放回去,declval然后尝试static_assert离开非。const ns::foons::foo::beginconst

于 2013-04-24T00:31:02.970 回答