Sfinae с decltype: ошибка в clang или gcc?

Clang-3.2 может компилироваться и кодировать поведение, как ожидалось:

struct have_f { int f(int i) {return 10;} }; 

struct empty {};

template <class T> 
struct outer {
        T t; 

        // if T have f(), define  outer_f()
        template<class U=decltype(t.f(1))> 
        int outer_f(int i) { return t.f(i); }
};

int main() {
        outer<have_f>  o1;
        outer<empty>   o2;

        // to silence unused var warning
        return  o1.outer_f(10) + sizeof(o2); 
}

GCC любой версии отклоняется с помощью:

t.cc:13:6: error: ‘struct empty’ has no member named ‘f’
  int outer_f(int i) { return t.f(i); }
      ^

Кто прав? Gcc или Clang?

Заметьте, что был похожий вопрос, без реального ответа.

Ответ 1

Я считаю, что проблема 14.6.3 [temp.nondep]:

1 - Не зависящие от имени имена, используемые в определении шаблона, определяются обычным поиском имен и связаны в той точке, в которой они используются.

В приведенном примере описывается, что плохо сформированное выражение в определении шаблона "может быть диагностировано либо [в определении шаблона], либо в момент создания экземпляра".

Шаблон-аргумент по умолчанию (14.1p9) U=decltype(t.f(1)) является необязательным именем в контексте создания struct outer (то есть он не зависит от аргумента шаблона к его собственному шаблону), поэтому он плохо сформирован для экземпляра struct outer с T = struct empty. В стандарте явно не указано, где оцениваются аргументы шаблона по умолчанию, но единственным разумным заключением является то, что они рассматриваются как любая другая конструкция и оцениваются в той точке, в которой они происходят (или, в этом примере, в момент создания struct outer). Я не вижу никакой широты для компилятора, чтобы отложить оценку необязательных аргументов шаблона по умолчанию в контекстах, где применяется SFINAE.

К счастью, решение легко: просто сделайте аргумент template-argument по умолчанию U зависимым именем:

    // if T have f(), define  outer_f()
    template<class T2 = T, class U=decltype(static_cast<T2 &>(t).f(1))> 
    int outer_f(int i) { return t.f(i); }