С++: вложенный класс класса шаблона

Рассмотрим следующий код:

template < typename T >
struct A
{
    struct B { };
};

template < typename T >
void f( typename A<T>::B ) { }

int main()
{
    A<int>::B x;
    f( x );         // fails for gcc-4.1.2
    f<int>( x );    // passes
    return 0;
}

Итак, здесь gcc-4.1.2 требует, чтобы аргумент шаблона f явно указывался. Соответствует ли это стандарту? Имеются ли в новых версиях GCC эта проблема? Как я могу избежать явного указания int при вызове f?

Update: Вот обходной путь.

#include <boost/static_assert.hpp>
#include <boost/type_traits/is_same.hpp>

template < typename T >
struct A
{
    typedef T argument;
    struct B { typedef A outer; };
};

template < typename T >
void f( typename A<T>::B ) { }

template < typename Nested >
void g( Nested )
{   
    typedef typename Nested::outer::argument TT;
    BOOST_STATIC_ASSERT( (boost::is_same< typename A<TT>::B, Nested >::value) );
}

struct NN 
{
    typedef NN outer;
    typedef NN argument;
};

int main()
{
    A<int>::B x;
    NN y;
    g( x );  // Passes
    g( y );  // Fails as it should, note that this will pass if we remove the type check
    f( x );  // Fails as before

    return 0;
}

Однако, я все еще не понимаю, почему вызов f( x ); недействителен. Можете ли вы ссылаться на какой-то момент в стандарте, который говорит, что такой вызов должен быть недействительным? Можете ли вы привести пример, когда такой вызов неоднозначен?

Ответы

Ответ 1

typename A<T>::B

Здесь T находится в неразрешенном контексте, что означает, что T не может быть выведено из аргумента функции.

Проблема в том, что в общем случае существует потенциально бесконечное количество возможных типов T, которые могли бы совпадать. Рассмотрим, например, если вместо struct B { }; вы имели typedef int B;.

Ответ 2

Как я могу избежать явного указания int при вызове f?

Просто сделайте B объявить свой тип типа вложенности

template < typename T >
struct A
{
    struct B { typedef A outer; };
};

Тогда вы можете вывести его. Следующее занимает внешний шаблон, внутренний typedef и тип возврата

template<template<typename> class Outer, typename D, typename R = void >
struct nesting { };

template<template<typename> class Outer, typename Arg, typename R>
struct nesting< Outer, Outer<Arg>, R > {
  typedef Arg arg1_type;
  typedef R type;
};

template < typename T >
typename nesting<A, typename T::outer>::type
f(T) { 
  /* nesting<A, typename T::outer>::arg1_type is A T */ 
}

Ответ 3

Как я могу избежать явного указания int при вызове f?

Вам понадобится небольшая помощь от struct B.

template < typename T >
struct A
{
    struct B 
    { 
        static T getType(); // no impl required 
    };
};

#define mytypeof(T) (true?0:T)

template < typename T, typename U >
void f( T t, U ) { } // U will be T of A<T>::B

Вызов со следующим:

f(x, mytypeof(x.getType()));

В качестве альтернативы вы можете абстрагироваться от mytypeof(x.getType()), добавив еще одну функцию, которую вы вызываете f, чтобы вы могли иметь свой оригинальный f(x). например.

template < typename T, typename U >
void b( T t, U ) { } // U will be T of A<T>::B

template < typename T >
void f( T t )
{
    b(t, mytypeof(t));
}

Затем вы можете вызвать f(x).

Ответ 4

В связи с вопросом в "Обновить" здесь возникает ситуация, при которой вызов f был бы неоднозначным (если бы он был разрешен, то есть):

// Definitions of generic "struct A", as well as "f()", are the same as above

// But additionally, consider a specialized "struct A", defined as follows:

template <>
struct A<double>
{
    typedef A<int>::B B;
}

// Now consider the call to "f", similarly to before:

int main()
{
    // Possibility 1 for argument to "f()"
    // A<int>::B x;

    // Possibility 2 for argument to "f()": Use the specialized version of "struct A"
    A<double>::B x;

    f(x); // which value to deduce for type T?  Could be "int" or "double"
}

Обратите внимание на двусмысленную пару возможных экземпляров f: как f<int>(), так и f<double> приведет к успешному вызову f().