2013-04-23 3 views
3

Рассмотрим этот простой тест SFINAE, чтобы определить, является ли тип может быть аргументом для 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() {} 

Обратите внимание, что array заголовка, в котором определен специализация для std::begin, входит после того, как Функции SFINAE. Утверждение терпит неудачу. Теперь, если я перемещаю #include <array> раньше, он работает. (gcc 4.8.0 20130411, clang version 3.2)

Я не понимаю, почему. Функции SFINAE являются шаблонами, не должны ли они быть задействованы при необходимости в статическом утверждении после включения заголовка, определяющего функцию, которую они тестируют?

Проблема заключается в том, что мой SFINAE быть в заголовке, я должен убедиться, что он включен после любого другого заголовка контейнера (этот вопрос конкретно не связан с заголовком array).

+0

Неопределенное поведение для добавления специализаций к шаблону после того, как шаблон уже был использован в другом месте. –

+0

@KerrekSB: Вы уверены, что это актуально в этом случае? В конце концов, первое использование, которое вызовет неявное создание, будет в 'static_assert' –

+0

@AndyProwl: я уверен, что на 90% или около того. Я считаю, что это обсуждалось раньше, и это тот вывод, к которому мы пришли. Конечно, я ошибаюсь. –

ответ

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

Upd ate:

Из комментариев (здесь и в 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 в коде.

Я использовал *((T*)0) вместо std::declval<T>(), потому что проблема со стабильностью.Чтобы это увидеть, положите declval назад и попробуйте static_assert за const ns::foo, оставив ns::foo::begin не const.

+0

Благодарим вас за подробный ответ. – manu

+0

И если я не совсем ошибаюсь (снова), в отношении комментариев @Andy Prowl, «первое использование» шаблона 'begin' является во время объявления функций SFINAE и, следовательно, любая специализация должна предшествовать. Другими словами, замена 'array' на' valarray' (или специальным классом, специализирующимся на 'begin'), сломает ваш первый пример, не так ли? (Я все еще обеспокоен порядком включения заголовков, последний пункт в моем исходном сообщении.) – manu

+0

По-видимому, это так, я боюсь :-(Например, 'static_assert (std_begin_callable (0)," failed "); 'будет срабатывать или не зависит от того, будет ли' class foo {}; namespace std {int begin (const foo &);} 'предшествует или следует определению' std_begin_callable'. –

Смежные вопросы