views:

346

answers:

5

How come I can't instantiate an object of type Foo with above constructor?

I have a class Bar that uses an internal typedef (as a workaround for "template typedefs") and intend to use it in a constructor as below (CASE 1). However, I don't seem to get it to compile. Is this legal C++? CASE 2 seems to suggest the problem is related to the typedef in Bar.

How can I define a constructor that will accept std::vectors of objects with the type in Bar?

#include <vector>
#include <iostream>
#include <utility>

template <typename T>
struct Bar
{
    typedef std::pair<T, T> type; // or anything else that uses T
};

struct Foo
{
    Foo() {}

    // CASE 1: doesn't compile
    template <typename T> explicit Foo( const std::vector<typename Bar<T>::type>& data )
    {
        std::cout << "Hello\n";
    }

    //// CASE 2: compiles, but it's not what I want
    //template <typename T> explicit Foo( const std::vector<Bar<T> >& data )
    //{
    //  std::cout << "Hello\n";
    //}
};

int main()
{
    std::vector<Bar<int>::type> v; // for CASE 1
    //std::vector<Bar<int> > v; // for CASE 2

    Foo f( v );
    return 0;
}
A: 

Hoist out the template keyword:

template <typename T>
struct Foo
{
    Foo() {}

    explicit Foo( const std::vector<typename Bar<T>::type>& data )
    {
        std::cout << "Hello\n";
    }
};

Then change the constructor to

Foo<int> f( v );
Greg Bacon
Ok, that would probably work, but I explicitly don't want to template the complete class!The type T should only be used to generate multiple constuctors, so that they can accept different kinds of input. The type has no further relation to Foo, therefore I don't want to use it outside.Note that if I write another (non-constructor) function with this parameter and leave a default constructor, the compiler doesn't complain anymore. But again, it's not what I want
MichaelH
Classes don't deduce their type from constructor parameters.
anon
+4  A: 

Your constructor is:

template <typename T> 
explicit 
Foo( const std::vector<typename Bar<T>::type>& data )

The template argument T cannot be deduced from the function argument this way. (I think this is called a "non-deducible context", but I'm not sure.)

This simply won't work. You'll have to write

template <typename B> 
explicit 
Foo( const std::vector<B>& data )

instead and find other ways to assert that B is of type typename Bar<T>::type.

sbi
Ah, Luc has kindly provided chapter and verse to my rather vague statement: http://stackoverflow.com/questions/2140025/c-templated-constructor-wont-compile/2140182#2140182
sbi
+8  A: 

According to paragraph 14.8.2.1 of the C++ standard, when a template parameter is used only in a non-deduced context, the corresponding template argument cannot be deduced:

If a template-parameter is not used in any of the function parameters of a function template, or is used only in a non-deduced context, its corresponding template-argument cannot be deduced from a function call and the template-argument must be explicitly specified.

The definition of nondeduced contexts, as stated in §14.8.2.4:

The nondeduced contexts are:

  • the nested-name-specifier of a type that was specified using a qualified-id.

  • A type that is a template-id in wich one or more of the template-arguments is an expression that references a template-parameter.

In Bar<T>::type, Bar<T> is a nested-name-specifier and hence a non-deduced context, so you must explicitly specify the template argument when calling the constructor...which is not possible (i.e. you cannot write Foo f<int>(v)).

I suppose the compiler cannot deduce the template argument because that would be at least cumbersome, and more probably impossible: imagine Bar is specialized:

template<typename T>
struct Bar
{
    typedef std::pair<T,T> type;
};

template<>
struct Bar<char>
{
    typedef std::pair<int,int> type;
};

Now I have an ambiguity when calling Foo's constructor with std::vector<std::pair<int,int> >: should the template argument be int or char? And even if there was no such ambiguity, you can easily see that the compiler would have to instantiate Bar with potentially any type before finding the instantiation with the correct typedef (well, I'm not so sure above statements are truly relevant since I often find out compilers to be much more smarter than I thought :-)!)

Luc Touraille
You might also cite 14.8.2.4/4, which names the two nondeduced contexts.
James McNellis
Thanks for the pointer, I added the information.
Luc Touraille
A: 

I think the only reason for that is that you want to instantiate a Bar of corresponding type instead of printing "hello".

Perhaps you could try mapping the types in the reversed direction (the kind of reversed deduction that you are hoping the compiler would be able to perform):

#include <utility>
#include <vector>

template <class T>
struct BarType;

template <class T>
struct BarType<std::pair<T, T> >
{
    typedef T type;
};

template <class T>
struct Bar {};

struct Foo
{
    template <class T>
    Foo(const std::vector<T>& )
    {
        Bar<typename BarType<T>::type> bar;
        //...
    }
};

int main()
{
    Foo(std::vector<std::pair<int, int> >());
}
UncleBens
A: 

Could something like this work for you?

#include <vector>
#include <iostream>
#include <utility>
#include <boost/static_assert.hpp>

template <typename T>
struct Bar
{
    typedef std::pair<T, T> type; // or anything else that uses T
    enum {val = 42};
};

template <typename T>
struct Traits
{
    enum {allowed = false};
};

template <typename T>
struct Traits<std::pair<T, T> >
{
    enum {allowed = true};
    typedef Bar<T> BarType;
};

struct Foo
{
    Foo() {}

    template <typename T> explicit Foo( const std::vector<T>& data )
    {
        BOOST_STATIC_ASSERT(Traits<T>::allowed);
        typedef typename Traits<T>::BarType BarType;
        std::cout << BarType::val << std::endl;
    }
};

int main()
{
    std::vector<Bar<int>::type> v;
    std::vector<float> v2;

    Foo f( v );
//    Foo f2( v2 ); // Compile error
    return 0;
}

This compiles and runs on GCC 4.4.1. You can specialize Traits for other vector::value_type's that are allowed by your constructor.

Emile Cormier