Подтвердить что ты не робот

Почему использование двух sizeofs работает, чтобы проверить, является ли класс конструктивным по умолчанию, но нет?

Я использовал код из Есть ли способ проверить, имеет ли класс С++ конструктор по умолчанию (кроме атрибутов типа компилятора)?".

Я немного изменил его, чтобы работать со всеми моими тестовыми примерами:

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;


    // the second version does not work
#if 1
    template<int x, int y> class is_equal {};
    template<int x> class is_equal<x,x> { typedef void type; };

    template< class U >
    static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );
#else
    template<int x> class is_okay { typedef void type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof U() >::type * );
#endif

    template< class U >
    static no sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(yes) };
};

Почему он корректно работает с двумя версиями аргументов шаблона, но не с обычным (set #if 0)? Это ошибка компилятора? Я использую Visual Studio 2010.

Я использовал следующие тесты:

BOOST_STATIC_ASSERT( is_default_constructible<int>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( !is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );

Я действительно в растерянности:

  • Два теста не работают с другой версией: int[100] и NotDefaultConstructible. Все тесты успешны с двумя версиями аргументов шаблона.
  • Visual Studio 2010 не поддерживает std::is_default_constructible. Однако, мой вопрос заключается в том, почему существует какая-либо разница в двух реализациях и почему одна работает, а другая - нет.
4b9b3361

Ответ 1

(Мой ответ в значительной степени информирован о предыдущем ответе DS.)

Прежде всего обратите внимание, что у вас class is_okay { typedef void type; }, т.е. type является частным членом is_okay. Это означает, что он фактически не виден вне класса и, следовательно,

template< class U >
static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );

никогда не удастся. Однако SFINAE изначально не применялась к этой ситуации в С++ 98; только до разрешения DR 1170 "проверка доступа [была выполнена] как часть процесса замещения". [1]

(Удивительно, но Паоло Карлини написал эту запись в блоге всего 10 дней назад, поэтому ваше время с этим вопросом безупречно. В таких случаях, по словам Карлини, GCC до 4.8 вообще не выполнял проверку доступа во время SFINAE. Таким образом, это объясняет, почему GCC не жаловался на приватность type. Вам нужно было бы использовать GCC из верхнего дерева, буквально менее двух недель назад, чтобы увидеть правильное поведение.)

Clang (top-of-tree) следует за DR в режиме -std=c++11, но дает ожидаемую ошибку в своем режиме С++ 03 по умолчанию (т.е. Clang не следует за DR в режиме С++ 03). Это немного странно, но, возможно, они делают это для обратной совместимости.

Но в любом случае вы фактически не хотите, чтобы type был приватным в первую очередь. То, что вы хотели написать, - struct is_equal и struct is_okay.

С этим изменением, Кланг передает все ваши тестовые примеры. GCC 4.6.1 также передает все ваши тестовые примеры, за исключением int[100]. GCC считает, что int[100] в порядке, тогда как вы утверждаете, что это не нормально.

Но еще одна проблема с вашим кодом заключается в том, что он не тестирует то, что, по вашему мнению, он тестирует. Стандарт С++, статья 8.5 # 10, говорит очень четко: [2]

Объектом, инициализатором которого является пустое множество скобок, т.е. (), должно быть инициализировано значение.

Итак, когда вы пишете sizeof U(), вы не тестируете, если U может быть инициализирован по умолчанию; вы проверяете, может ли быть инициализировано значение!

... Или вы? По крайней мере, в некоторых из моих тестовых случаев сообщения об ошибках GCC указывали, что U() интерпретируется как имя типа - "функция, возвращающая U", и именно поэтому int[100] вел себя по-другому. Я не вижу, как это поведение действительно, но я действительно не понимаю здесь синтаксических тонкостей.

Если вы действительно хотите проверить инициализацию по умолчанию, вы должны использовать что-то вроде sizeof *new U везде, где у вас есть sizeof U().

Кстати, int[100] есть default-initializable, period. В стандарте ясно, что означает инициализация типа массива по умолчанию.

Наконец, я задаюсь вопросом, является ли одна из причин дурацкого поведения в вашем коде, что вы пытаетесь передать неприкрашенный 0 (который имеет тип int) функции, набор перегрузок которой включает одну функцию, t222 > и взяв .... Я мог бы полностью понять, выбрал ли компилятор неправильный в этом случае. Вам лучше посоветовать передать 0 функции, принимающей int.

Объединяя все вместе, вот версия вашего кода, которая отлично работает для меня (т.е. никаких отказов утверждения) как в ToT Clang, так и в GCC 4.6.1.

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;

    template<int x> struct is_okay { typedef int type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof (*new U) >::type );

    template< class U >
    static no sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(yes) };
};

#if __has_feature(cxx_static_assert)
#define BOOST_STATIC_ASSERT(x) static_assert(x, "or fail")
#else
#define dummy2(line) dummy ## line
#define dummy(line) dummy2(line)
#define BOOST_STATIC_ASSERT(x) int dummy(__COUNTER__)[(x) - 1]
#endif

#include <string>

BOOST_STATIC_ASSERT( !is_default_constructible<int()>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );

Ответ 2

Это почти наверняка артефакт (ошибка) компилятора, поскольку g++ ведет себя (и терпит неудачу) по-разному. Я могу только догадываться, почему VS ведет себя по-разному, но можно предположить, что разумным является то, что этот класс:

template<int x> class is_okay { typedef void type; };

имеет такое же определение независимо от параметра шаблона, поэтому, возможно, компилятор пропускает шаг при анализе static sfinae( typename is_okay< sizeof U() >::type * ); и считает его корректным, не глядя на параметр is_okay. Поэтому он считает, что все по умолчанию является конструктивным.

Почему ни VS, ни g++ не беспокоится о том, что is_okay::type является закрытым, я не знаю. Похоже, они оба должны быть.

g++, с другой стороны, рассматривает обе версии как эквивалентные. В обоих случаях, однако, это дает другую ошибку для int[100]. Этот вопрос является спорным относительно того, должен ли он быть конструктивным по умолчанию. Кажется, вы думаете, что этого не должно быть. g++ 47 std::is_default_constructible думает! Чтобы получить это поведение (что, скорее всего, более стандартно), вы можете заменить T на typename boost::remove_all_extents<T>::type в строке enum.