Почему удаление параметра по умолчанию нарушает этот счетчик constexpr?

рассмотрим следующий код, реализующий счетчик времени компиляции.

#include <iostream>

template<int>
struct Flag { friend constexpr int flag(Flag); };

template<int N>
struct Writer
{
    friend constexpr int flag(Flag<N>) { return 0; }
};

template<int N>
constexpr int reader(float, Flag<N>) { return N; }

template<int N, int = flag(Flag<N>{})>
constexpr int reader(int, Flag<N>, int value = reader(0, Flag<N + 1>{}))
{
    return value;
}

template<int N = reader(0, Flag<0>{}), int = sizeof(Writer<N>) >
constexpr int next() { return N; }


int main() {
    constexpr int a = next();
    constexpr int b = next();
    constexpr int c = next();
    constexpr int d = next();
    std::cout << a << b << c << d << 'n'; // 0123
}

второй reader перегрузка, если я помещаю параметр по умолчанию в тело функции, например:

template<int N, int = flag(Flag<N>{})>
constexpr int reader(int, Flag<N>)
{
    return reader(0, Flag<N + 1>{});
}

тогда выход станет:

0111

Почему это происходит? Почему вторая версия больше не работает?

если это имеет значение, я использую Visual Studio 2015.2.

2 ответов


без value передается в качестве параметра, ничто не мешает компилятору кэширование вызов reader(0, Flag<1>).

в обоих случаях первый next() вызов будет работать, как ожидалось, так как это немедленно приведет к SFINAEing к reader(float, Flag<0>).

второй next() оценить reader<0,0>(int, ...), от которой зависит reader<1>(float, ...) который может быть кэширован, если он не зависит от


актуальность value заключается в том, что она участвует в разрешении перегрузки. В соответствии с правилами SFINAE ошибки создания экземпляра шаблона молча исключить кандидатов из разрешения перегрузки. Но он создает экземпляр Flag<N+1>, что заставляет разрешение перегрузки стать жизнеспособным в следующий раз (!). Таким образом, вы фактически подсчитываете успешные экземпляры.

почему ваша версия ведет себя по-другому? Вы все еще ссылаетесь Flag<N+1>, но и в реализация функции. Это важно. С помощью шаблонов функций,декларация должно быть рассмотрено для SFINAE, но только выбранная перегрузка затем создается экземпляр. Ваше заявление просто template<int N, int = flag(Flag<N>{})> constexpr int reader(int, Flag<N>); и не зависит от Flag<N+1>.

как отмечено в комментариях, не рассчитывайте на этот счетчик;)