考虑这个简单的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
标题)。
答案 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_callable
或std::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
。