4

I am trying to use a templated function to return the default value for a parameter in the constructor of a template class. The template parameters for the function are also template parameters for the class. I provided an example below.

Background The example shows the exact use case and dependencies as they also occur in my application. Class X is actually a quite big class which manages a big data block which is split into smaller blocks. Class Helper is a memory manager which allocates and frees memory in the size of the smaller blocks. In reality, the GetHelper function would try to deduce some constructor parameters of Helper at runtime, so this is why I used this design.

Actual question When both macros USE_NS and SHOW_ERROR are defined, the code doesn´t compile, giving an error C2783 could not deduce template argument at line 66. This is where I try to initialize a constructor parameter with the templated function GetHelper (providing the template parameters!). Remember, GetHelper is from a different namespace than the class X. Please also mind, that in line 72 the same function call is used, to initialize the Helper object inside the ctor body. What is wrong? Is there a solution or workaround?

I am using Visual Studio 2008 Pro, and boost 1.47.

#include <iostream>
#include <boost/shared_ptr.hpp>

// 1 means macro is defined
// USE_NS 1 and SHOW_ERROR 1 -> Compileerror
// USE_NS 0 and SHOW_ERROR 1 -> Works, Output: 8 and 16.5
// USE_NS 1 and SHOW_ERROR 0 -> Works, Output: 3
// USE_NS 0 and SHOW_ERROR 0 -> Works, Output: 3


#define USE_NS
#define SHOW_ERROR


#ifndef USE_NS
#define NH 
#define NX
#endif

#ifdef USE_NS
namespace NH
{
#endif

template< typename TNumAtH, size_t TSizeAtH>
class Helper
{
public:
    TNumAtH* x;

    Helper()
    {
        x = new TNumAtH[TSizeAtH];
        x[0] = static_cast< TNumAtH >( 8 );
    }

    ~Helper()
    {
        delete [] x;
    }
};

template <typename TNumAtF, size_t TSizeAtF >
boost::shared_ptr< Helper< TNumAtF, TSizeAtF > > GetHelper()
{
    return boost::shared_ptr< Helper< TNumAtF,TSizeAtF > > ( new Helper< TNumAtF, TSizeAtF >() );
}

#ifdef USE_NS
}

namespace NX
{
#endif

template< typename TNumAtX, size_t TSize >
class X
{
public:
    boost::shared_ptr< NH::Helper< TNumAtX, TSize > > a;

#ifdef SHOW_ERROR
    //this produces an error if namespace are used, if no namespaces are used
    //NH is reduced to a blank (see macros at line 17 and 18
    X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h = NH::GetHelper< TNumAtX, TSize >() );
#endif

    X( TNumAtX firstElem )
    {
        //this works with namespaces and without namespaces
        a = NH::GetHelper< TNumAtX, TSize >();
        a->x[0] = firstElem;
    }

    TNumAtX GetNum()
    {
        return a->x[0];
    }

};

#ifdef SHOW_ERROR
template< typename TNumAtX, size_t TSize >
X<TNumAtX, TSize>::
X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h ) : a( h )
{
}
#endif

#ifdef USE_NS
}
#endif


int main( int argc, char** argv )
{
    std::cout << "Hello at TestTemplateFunction" << std::endl;
    //use case one
#ifdef SHOW_ERROR
    NX::X<int, 5> x1;
#else
    NX::X<int, 5> x1( 3 );
#endif
    std::cout << "Use case 1: " << x1.GetNum() << std::endl;

    //use case two
#ifdef SHOW_ERROR
    typedef float T;
    size_t const N = 9;
    boost::shared_ptr< NH::Helper<T, N> > h( new NH::Helper<T, N> );
    h->x[0] = 16.5f;
    NX::X<T, N> x2( h );
    std::cout << "Use case 2: " << x2.GetNum() << std::endl;
#endif

    std::cout << "Hit the any key" << std::endl;
    getchar();
    return 0;  
}

and here is the CMakeLists.txt file

PROJECT(TestTemplateFunction)
CMAKE_MINIMUM_REQUIRED( VERSION 2.8 )

FIND_PACKAGE( BOOST )

INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR} )

ADD_EXECUTABLE( TestTemplateFunction main.cpp  )

Edit: The compile error

main.cpp(65) : error C2783: 'boost::shared_ptr<NH::Helper<TNumAtH,TSizeAtH>>    NH::GetHelper(void)' : could not deduce template argument for 'TNumAtF'
main.cpp(44) : see declaration of 'NH::GetHelper'
main.cpp(65) : error C2783: 'boost::shared_ptr<NH::Helper<TNumAtH,TSizeAtH>>       NH::GetHelper(void)' : could not deduce template argument for 'TSizeAtF'
main.cpp(44) : see declaration of 'NH::GetHelper'
GeorgT
  • 160
  • 1
  • 9
  • Just to emphasize one thing: If you remove namespaces (e.g. by not defining USE_NS), the code compiles and works as intented. – GeorgT Jan 18 '13 at 15:23
  • Richard Smith (from Clang) took the time to answer me, this is a C++ defect (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#325). You should surround the default value expression by parentheses to get rid of the ambiguity in parsing. – Matthieu M. Jan 18 '13 at 18:00

3 Answers3

2

Breaking news

After reporting the issue on Clang 3.2, it appears this is actually a C++ defect. You see the bug discussion in the clang database.

Quoting Richard Smith:

This is a defect in C++ itself:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#325

Clang matches the behavior of all other C++ compilers here; this is not a bug (though depending on the way in which that issue is resolved, we may need to revisit this).

The workaround is to add parentheses around the default argument expression.

The issue is one of ambiguity. For example, by looking at:

int = a < b, c < d > ( e )

... is one parameter, and ...

int = a < b, c < d > ( e ) = 0

we can realize it is not easy to distinguish between template parameters, regular < calls and what therefore constitutes the default argument.

If we follow Richard's suggestion and add parentheses, then we are no longer subject to compiler whims. Or at least, I can guarantee Clang gets it right (revision 6):

 X( boost::shared_ptr< NH::Helper<T, N> > h = (NH::GetHelper<T, N>()) );

It looks to me like a compiler bug.

I tested your code (thanks for posting a complete example by the way) on liveworkspace and gcc 4.7.2 produces the following output:

Hello at TestTemplateFunction
Use case 1: 8
Use case 2: 16.5

while clang 3.2 produces:

Compilation finished with errors:
source.cpp:65:86: error: unknown type name 'TSize'
X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h = NH::GetHelper< TNumAtX, TSize >() );
                                                                                 ^
source.cpp:65:92: error: expected ')'
X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h = NH::GetHelper< TNumAtX, TSize >() );
                                                                                       ^
source.cpp:65:6: note: to match this '('
X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h = NH::GetHelper< TNumAtX, TSize >() );
 ^
source.cpp:65:84: error: expected '>'
X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h = NH::GetHelper< TNumAtX, TSize >() );
                                                                               ^
source.cpp:85:1: error: out-of-line definition of 'X<TNumAtX, TSize>' does not match any declaration in 'X<TNumAtX, TSize>'
X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h ) : a( h )
^
4 errors generated.

I modified the example slightly, in two ways (change 5):

  • introduced static boost::shared_ptr<Helper> Get() { return boost::shared_ptr<Helper>(new Helper()); } inline in the Helper template

  • made a typedef NH::Helper< TNumAtX, TSize > Helper in X and used that for the constructor X(boost::shared_ptr<Helper> h = Helper::Get());

and now clang manages to compile the code.

I managed to reduce the error (of clang) to:

template <typename T, unsigned N>
struct Helper {};

template <typename T, unsigned N >
Helper< T, N > GetHelper() { return Helper< T, N > (); }

template < typename T, unsigned N >
struct X {
    X( Helper< T, N > h = GetHelper<T, N>() ) {}
};

you can see it here.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Funny; I just did almost exactly the same steps you did. Same results as you for g++-4.8 and clang-3.2. – Nate Kohl Jan 18 '13 at 17:11
  • Thanks for the thorough analysis and the suggestion. I could reproduce everything, but on Visual Studio 2008 it still doesn´t work. I also tried it with VS 2012 where the compilation results in the same error. – GeorgT Jan 21 '13 at 08:24
  • A collegue of mine suggested to wrap the function call into a functor. This works in clang (http://liveworkspace.org/code/3EALA5$36) and also in VS 2008. – GeorgT Jan 21 '13 at 10:02
1

This is a really weird situation which I still don't understand why it will behave this way when given a namespace.

But, I've found a workaround you might use.
Just declare you're using the namespace, and remove the namespace scope before the function.

namespace NX
{
using namespace NH;
#endif

template< typename TNumAtX, size_t TSize >
class X
{
public:
    boost::shared_ptr< NH::Helper< TNumAtX, TSize > > a;

#ifdef SHOW_ERROR
    //this produces an error if namespace are used, if no namespaces are used
    //NH is reduced to a blank (see macros at line 17 and 18
            X( std::shared_ptr< NH::Helper< TNumAtX, TSize > > h = GetHelper<TNumAtX, TSize> () );
#endif
Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
0

The reason your compiler is throwing this is because you have an ambiguity between template arguments. The helper class requires template arguments, and so does x. The compiler does not know how to build a Helper, because the parameters supplied is another set of template arguments.

Your constructor for x:

X( boost::shared_ptr< NH::Helper< TNumAtX, TSize > > h = NH::GetHelper< TNumAtX, TSize >() );

is trying to assign a default value to a template member. It does not know which template arguments are being supplied to Helper and GetHelper. Even though the the templates are the same, you must explicitly state the template parameters from x are being passed in as template arguments for Helper and GetHelper.

There are some insightful answers about template template parameters in this post: Template Template Parameters

Also, based on the msdn website, there is this statement about C2783: MSDN

The compiler cannot determine a template argument. Default arguments cannot be used to deduce a template argument.

Community
  • 1
  • 1
Igneous01
  • 719
  • 1
  • 10
  • 24