考虑一下这个简单的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标头(其中定义了std::begin的专门化)包含在SFINAE函数之后。断言失败。现在,如果我之前移动#include <array>,它就能工作了。(gcc 4.8.0 20130411,clang版本3.2)
我不明白为什么。SFINAE函数是模板,是否应该在静态断言中,在包含定义测试函数的头后,在需要时实例化它们?
问题是,我的SFINAE在头中,我必须确保它包含在任何其他容器头之后(这个问题不是专门链接到array头)。
发布于 2013-04-24 00:31:02
正如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>的包含间接地意味着包含了定义std::begin(std::initializer_list<T>)的<initializer_list>。因此,在这个翻译单元中,名称std::begin是可见的。
但是,当您调用std_begin_callable时,第一个重载是SFINAEd离开的,因为可见的std::begin不能接受std::array。
现在,如果您完全删除<iterator>和<utility>的包含(在std_begin_callable之后保留<array> ),那么编译将失败,因为编译器将不再看到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 () {}最后,您可以使用以下方法复制/简化以前的错误行为:
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 for const ns::foo离开ns::foo::begin non-const。
https://stackoverflow.com/questions/16164650
复制相似问题