2013-04-23 56 views
3

的顺序考虑这个简单SFINAE测试,以确定是否一个类型可以是,所述array头,其中,用于std::begin专业化被定义,包括std::beginSFINAE和定义

#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功能。断言失败。现在,如果我之前移动#include <array>,它的工作原理。 (GCC 4.8.0 20130411,铛版本3.2)

我不明白为什么。该SFINAE功能被模板,不应该他们在需要时实例化,在静态断言,列入标题定义他们测试的功能之后?

的问题是,我在SFINAE的标题是,我必须确保它包含任何其他容器头之后(这个问题没有具体联系到array头)。

+0

这是未定义的行为,在模板已经用于其他地方之后向模板添加专业化。 – 2013-04-23 08:38:01

+0

@KerrekSB:你确定在这种情况下是相关的吗?毕竟,会导致隐式实例化的第一次使用是在'static_assert' – 2013-04-23 08:42:16

+0

@AndyProwl:我很喜欢90%左右。我相信这已经在之前讨论过了,这就是我们得出的结论。当然,我可能是错的。 – 2013-04-23 08:44:41

回答

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>,其定义为std::begin(std::initializer_list<T>)。因此,在此翻译单元中,名称std::begin可见。

但是,当您拨打std_begin_callable时,第一个过载被SFINAEd带走,因为可见std::begin不能接受std::array。现在

,如果您删除的<iterator><utility>共夹杂物(保持<array>std_begin_callable后),然后编译失败,因为编译器将不再看到std::beginstd::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() {} 

UPD吃了:

从评论(这里和在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 S IN的代码。

我用*((T*)0)而不是std::declval<T>(),因为一个常量问题。要看到它,请将declval退回,然后尝试static_assert替代const ns::foo,并将ns::foo::begin退出非const

+0

感谢您的详细解答。 – manu 2013-04-24 08:05:48

+0

如果我还没有完全弄错(关于@Andy Prowl的评论),“begin”模板的“首次使用”是在SFINAE函数的声明期间进行的,因此任何专业化都应该在之前进行。换句话说,用'valarray'(或者一个专门定义'begin'的自定义类来替换'array')会破坏你的第一个例子,不是吗? (我仍然关心包含标题的顺序,最后一点在我原来的帖子中。) – manu 2013-04-24 08:35:26

+0

显然是这样,我害怕:-(例如,'static_assert(std_begin_callable (0),“failed” );''会根据'class foo {};命名空间std {int begin(const foo&);}'是否在std_begin_callable的定义之前或之后开始或者不开始。 – 2013-04-24 09:02:53