SFINAE和定义顺序

时间:2013-04-23 08:35:58

标签: c++ c++11 sfinae

考虑这个简单的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 () {}

请注意,在 SFINAE函数之后包含了array标题,其中定义了std::begin的特化。断言失败了。现在,如果我之前移动#include <array>,它就可以了。 (gcc 4.8.0 20130411,clang version 3.2)

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

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

1 个答案:

答案 0 :(得分: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>的{​​{1}}。因此,在此翻译单元中,名称std::begin(std::initializer_list<T>)可见。

但是,当您致电std::begin时,第一次重载是SFINAEd,因为可见std_begin_callable无法使用std::begin

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

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 () {}

<强>更新

从评论(此处和OP中)我认为不可能以您想要的方式解决头文件顺序问题。那么,让我建议一个基于ADL的解决方案,它接近一个解决方案,可能(但可能不是),足以满足您的使用案例:

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 () {}

它似乎有用但我还没有经过全面测试。我建议你在代码中使用/不使用注释掉的// <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::foo离开ns::foo::begin非 - const