0

I needed a special kind of variadic container and had some unforseen problems, so I created the following minimal example (see comments):

#include <iostream>
#include <tuple>

//---------------------------------------------------
template<class...Ts>
class container
{
    using ts_type = std::tuple<Ts...>;

public:
    container() = default;

    container(Ts... ts) : ts_{std::move(ts)...} {}

    // get #1: member template
    template<std::size_t index>
    auto 
    get() const -> decltype(std::get<index>(std::declval<const ts_type>())) {
        return std::get<index>(ts_);
    }

    // get #2: friend function
    template<std::size_t index>
    friend typename std::tuple_element<index,ts_type>::type
    get(const container& c) {
        return std::get<index>(c.ts_);
    }

    // get #3: friend function with type deduction
    // won't compile 
    // error: 'const class container<int, double>' has no member named 'ts_'
    //
    // template<std::size_t index>
    // friend auto 
    // get(const container& c) -> decltype(std::get<index>(c.ts_)) {
    //  return std::get<index>(c.ts_);
    // }


private:
    ts_type ts_;
};

//---------------------------------------------------
// WTF? g++ already complains about the declaration
// I'm not even trying to instantiate foo
template<class T>
void foo(const T& t) {
    // error: expected primary-expression before ')' token
    std::cout << t.get<0>() << std::endl;

    // error: 'get' was not declared in this scope
    std::cout << get<0>(t) << std::endl;
}

//---------------------------------------------------
int main() {   
    // this compiles and runs just fine ... as expected
    auto c = container<int,double>{1, 2.5};

    std::cout << c.get<0>() << std::endl;
    std::cout << c.get<1>() << std::endl;

    std::cout << get<0>(c) << std::endl;
    std::cout << get<1>(c) << std::endl;
}

What is going on here? Why does g++ (4.7.2) complain about the declarations inside foo? Why does container::get #3 not compile?
I guess things like get<0> can only be invoked on concrete types?
Is this behavior standard conforming?

  • First problem: the declaration of the `friend` function should be declared in the class and defined outside of it. Second: `t.get<0>()` needs to be `t.template get<0>()`: [See here](http://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords) – 0x499602D2 May 12 '14 at 20:30
  • Second problem: yeah - I was so stupid. – André Müller May 12 '14 at 20:55
  • The other little thing you have to do is give `container` template parameters, [**see here**](http://coliru.stacked-crooked.com/a/3fa0460e8140b1ae). – 0x499602D2 May 12 '14 at 21:05

2 Answers2

0

You have one fixable issue and one non fixable issue:

In foo you need to tell the compiler you are calling a template: std::cout << t.template get<0>() << std::endl;

Also in foo the freestanding function get depends on the instantiation of the container, which is not available at usage (You might explicitly instantiate the container before, but I consider that as no fix).

Another issue is the commented code (#3). Here I put the ts_type ts_ as a public member in front of get(const container& c) -> decltype(std::get<index>(c.ts_)), but that is no answering this issue.

  • Thanks for the t.template syntax! I know that's necessary when getting templated types out of a template class, but I didn't know you need that here too. The constructor is fine IMHO. I don't want it to be a template and have the proper argument types in the signature. And take-by-value-and-move-into-place does all I want. Your proposition is not perfect forwarding as far as I know: for that the ctor had to be a template container(OtherTs&&...) or am I wrong? And what do you mean 'depends on instantiation'? The error happens *before* any instantiation of foo takes place! – André Müller May 12 '14 at 20:43
0

OK, this works now! Thanks for the help!

#include <iostream>
#include <tuple>

//---------------------------------------------------
template<class...Ts>
class container
{
    using ts_type = std::tuple<Ts...>;

public:
    container() = default;

    container(Ts... ts) : ts_{std::move(ts)...} {}

    template<std::size_t index>
    auto 
    get() const -> decltype(std::get<index>(std::declval<const ts_type>())) {
        return std::get<index>(ts_);
    }

    template<std::size_t index, class... Us>
    friend auto 
    get(const container<Us...>& c) -> decltype(std::get<index>(c.ts_));

private:
    ts_type ts_;
};


template<std::size_t index, class... Us>
auto get(const container<Us...>& c) -> decltype(std::get<index>(c.ts_)) {
    return std::get<index>(c.ts_);
}


//---------------------------------------------------
template<class T>
void foo(const T& t) {
    std::cout << t.template get<0>() << std::endl;

    std::cout << get<0>(t) << std::endl;
}

//---------------------------------------------------
int main() {   
    auto c = container<int,double>{1, 2.5};

    std::cout << c.get<0>() << std::endl;
    std::cout << c.get<1>() << std::endl;

    std::cout << get<0>(c) << std::endl;
    std::cout << get<1>(c) << std::endl;

    foo(c);
}