1

I'm writing a pair wrapper. For the purposes of this question it can be simplified down to:

using namespace std;

template <class T1, class T2>
class myPair {
    pair<T1, T2> member;
public:
    myPair() = default;
    myPair(T1 x, T2 y) : member(make_pair(x, y)) {}
};

I'd like to be able to treat a myPair as an index-able container of size 2. To do this I'd obviously need to write an index operator for myPair. I'd like to do something like this, but my return type will depend upon a method parameter, and I can't use method parameters in meta-programming.

auto myPair::operator[](int index) {
    static_assert(index >= 0 && index < 2, "Index out of range");

    return get<index>(*this);
}

Obviously I could tackle this in the same way that pair does by providing a get function, but I'd like to me able to use the index operator syntax. Is there any way I can specialize a function template or use a method parameter to meta-program a template's return type?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • what's wrong with [`std::get`](http://en.cppreference.com/w/cpp/utility/pair/get)? – RamblingMad Aug 10 '15 at 15:45
  • @CoffeeandCode There is nothing wrong with `get`, but it means that a `pair` cannot be indexed in the same way as another container. – Jonathan Mee Aug 10 '15 at 16:13
  • @JonathanMee `pairs` and `tuples` are not Containers, in the C++StdLib sense of that concept. A capital-C Container is a homogeneous container, that is, all its elements have the same type. What exactly do you want to achieve, syntactically? (Including how you would like to use the return value.) – dyp Aug 10 '15 at 18:11
  • There are 3 ways to approach this. First, continuation passing style (where you take a continuation, and pass it the return value). Second, return a polymorphic visitable value (like `boost::variant`), which is similar. Finally, store the index in the type of the parameter. None of them are what you want: run-time values cannot be used to change the static type of an expression in C++, static types are (a subset of) what is known at compile time. – Yakk - Adam Nevraumont Aug 10 '15 at 18:25
  • @dyp I am trying to achieve the same thing that one would achieve by calling `get`, however `myPair` is going to be passed into a templatized function, so I need it to behave like other index accessible containers. The templatized function will access only one element of a container. assigning the result of this to a `const auto` variable and working on that variable. The templatized function does work fine with `get` it's just that I have to specialize it and I don't want to do that. – Jonathan Mee Aug 10 '15 at 18:25
  • @JonathanMee A `std::integral_constant` is convertible to `T`, so in theory you could use a variant of Quentin's approach (as Yakk suggested, replace Quentin's `idx_` with `integral_constant`) for both your pair type and StdLib Containers w/o specialization. E.g. `vector s(10); cout << s[10_c];` However, I'm worried about a concept that includes both StdLib Containers and heterogeneous containers alike. – dyp Aug 10 '15 at 19:04
  • @dyp error, array index out of bounds – Yakk - Adam Nevraumont Aug 10 '15 at 20:02
  • @JonathanMee the type of an expression is determined by the type of its arguments, and what its template arguments are (be they templates, types or values). If you want the type of an expression to depend on something else, you won't be able to do it. If you want to solve a practical problem, there are probably alternative solutions, a number of which have been presented based off guesses asto your practical problem. Providing your actual practical problem may generate more solutions. – Yakk - Adam Nevraumont Aug 10 '15 at 20:05
  • @Yakk The `_c` stands for octal interpretation, of course ;) – dyp Aug 10 '15 at 20:46

3 Answers3

4

It's almost possible. The integer literal can't be used directly as a constant-expression, but it can be wrapped in one, for example a template instantiation.

template <int> struct idx_ {};

template <char... C>
auto operator ""_i () {
    return idx_<(C - '0')...>{};
}

template <class T1, class T2>
class myPair {
    std::pair<T1, T2> member;
public:
    myPair() = default;
    myPair(T1 x, T2 y) : member(std::make_pair(x, y)) {}

    T1 &operator [] (idx_<0>) {
        return member.first;
    }

    T2 &operator [] (idx_<1>) {
        return member.second;
    }
};

int main() {
    myPair<int, std::string> mp(42, "Hi");
    std::cout << mp[0_i] << ", " << mp[1_i] << '\n';
}

Output:

42, Hi

Live on Coliru

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • @dyp I had thought about it, and convinced myself that it wasn't possible. Thanks for the doubt :p – Quentin Aug 10 '15 at 15:41
  • Boost.hana also contains an implementation: http://ldionne.com/hana/namespaceboost_1_1hana_1_1literals.html#a85ac3c47d02722a334181aab540e732c – dyp Aug 10 '15 at 16:04
  • @dyp I mean I gave it a second try, and succeeded :) – Quentin Aug 10 '15 at 16:04
  • d'oh! I didn't notice you've changed your answer! – dyp Aug 10 '15 at 16:05
  • @Quentin That's a really creative solution, but still not *exactly* what I was hoping for as I still cannot index like a regular container. – Jonathan Mee Aug 10 '15 at 17:36
  • @Quentin Is it possible something could be accomplished with implicit conversion/construction? – Jonathan Mee Aug 10 '15 at 17:47
  • [This answer looks familiar](http://stackoverflow.com/a/31901107/1774667) ;) Try using `std::integral_constant`, it has nice semantics. Also, you can eliminate duplication of `operator[]` by using `std::get(member)` and `std::tuple_element_t`. – Yakk - Adam Nevraumont Aug 10 '15 at 18:20
  • @Yakk That's some Wimax-level brainwave transmission here, haha ! I hadn't seen your answer beforehand, but that's a striking resemblance indeed. I find yours way nicer though, this one just covers the minimal functionality. – Quentin Aug 10 '15 at 19:52
  • @JonathanMee it's not possible if `T1` and `T2` are different, because you can't switch return types at runtime. Well, you could use Boost.Variant, but it gets ridiculously convoluted just to use a pair. – Quentin Aug 10 '15 at 19:53
2

No, it's not possible. The member function operator[] accepts non-constexpr objects, therefore making a compile time type detection pretty much impossible.

This will also make the static_assert not compile, for the same reason.

Shoe
  • 74,840
  • 36
  • 166
  • 272
1

You may use std::intergral_constant

template <std::size_t N>
const auto& operator[](std::integral_constant<std::size_t, N>) const {
    static_assert(N < 2, "Index out of range");

    return std::get<N>(member);
}

template <std::size_t N>
auto& operator[](std::integral_constant<std::size_t, N>) {
    static_assert(N < 2, "Index out of range");

    return std::get<N>(member);
}

Live Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Some brilliance that I don't understand? When I try to add this as a method of `myPair` I get: "error C2784: 'auto &myPair::operator [](std::integral_constant)': could not deduce template argument for 'std::integral_constant' from 'size_t'" Am I using this wrong? – Jonathan Mee Aug 10 '15 at 17:43
  • Just added a live Demo. (In fact it is a variant of Quentin's solution). – Jarod42 Aug 11 '15 at 00:01