9

I need to alias std::get function in order to improve readability in my code.

Unfortunately I got a compile-time error get<0> in namespace ‘std’ does not name a type. using is equivalent to typedef so it needs types to work with. I am using a std::tuple to represent some data type:

using myFoo = std::tuple<int,int,double,string>;
using getNumber = std::get<0>;

I look at some previous questions but the solution proposed is to wrap and use std::forward. I don't want to write such code for each member.

Is there a way to get around this using only using keyword?

Community
  • 1
  • 1
chedy najjar
  • 631
  • 7
  • 19

3 Answers3

10

is there a way to get around this using only using keyword?

I would say no, for std::get is not a type (thus it's not eligible for such an use).
Moreover, even if it was possible, note that std::get is an overloaded function, thus you would have been required to bind yourself to a specific implementation.

That said, in C++17, you can do something like this:

#include<tuple>
#include<utility>

using myFoo = std::tuple<int,int,double>;
constexpr auto getNumber = [](auto &&t) constexpr -> decltype(auto) { return std::get<0>(std::forward<decltype(t)>(t)); };

template<int> struct S {};

int main() {
    constexpr myFoo t{0,0,0.};
    S<getNumber(t)> s{};
    (void)s;
}

As you can see, constexpr lambdas and variables help you creating compile-time (let me say) wrappers you can use to rename functions.


As correctly pointed out by @T.C. in the comments, if you want to generalize it even more and get an almost perfect alias for std::get, you can use a variable template:

template<int N>
constexpr auto getFromPosition = [](auto &&t) constexpr -> decltype(auto) { return std::get<N>(std::forward<decltype(t)>(t)); };

Now you can invoke it as it follows:

S<getFromPosition<0>(t)> s{};

See it on wandbox.

skypjack
  • 49,335
  • 19
  • 95
  • 187
4

You can do it with a using + an enum:

#include<tuple>

using myFoo = std::tuple<int,int,double>;

int main() {
    constexpr myFoo t{0,0,0.};
    enum { Number = 0 };
    using std::get;

    auto&& x = get<Number>(t);
    (void)x;
}

Although unfortunately, this is not DRY since you have to maintain an enum and a tuple simultaneously.

In my view, the most DRY and safest way to achieve this is to use tagged values in the tuple. Limit the tuple to maximum one of each tag type.

The tag is essentially a mnemonic for some unique concept:

#include <tuple>
#include <iostream>

//
// simple example of a tagged value class
//
template<class Type, class Tag>
struct tagged
{
    constexpr tagged(Type t)
        : value_(t) {}

    operator Type&() { return value_; }

    operator Type const&() const { return value_; }

    Type value_;
};

struct age_tag {};
struct weight_tag {};
struct height_tag {};

using Age = tagged<int, age_tag>;
using Weight = tagged<int, weight_tag>;
using Height = tagged<double, height_tag>;

int main()
{
    constexpr auto foo1 = std::make_tuple(Age(21), Weight(150), Height(165.5));
    constexpr auto foo2 = std::make_tuple(Weight(150), Height(165.5), Age(21));
    using std::get;

    //
    // note below how order now makes no difference
    //

    std::cout << get<Age>(foo1) << std::endl;
    std::cout << get<Weight>(foo1) << std::endl;
    std::cout << get<Height>(foo1) << std::endl;

    std::cout << "\n";

    std::cout << get<Age>(foo2) << std::endl;
    std::cout << get<Weight>(foo2) << std::endl;
    std::cout << get<Height>(foo2) << std::endl;
}

expected output:

21
150
165.5

21
150
165.5
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

In general, tuple should be used in generic code.

If you know field 1 is a Number or a Chicken, you shouldn't be using a tuple. You should be using a struct with a field called Number.

If you need tuple-like functionality (as one does), you can simply write as_tie:

struct SomeType {
  int Number;
  std::string Chicken;

  auto as_tie() { return std::tie(Number, Chicken); }
  auto as_tie() const { return std::tie(Number, Chicken); }
};

Now you can access SomeType as a tuple of references by typing someInstance.as_tie().

This still doesn't give you < or == etc for free. We can do that in one place and reuse it everywhere you use the as_tie technique:

struct as_tie_ordering {
  template<class T>
  using enable = std::enable_if_t< std::is_base_of<as_tie_ordering, std::decay_t<T>>, int>;

  template<class T, enable<T> =0>
  friend bool operator==(T const& lhs, T const& rhs) {
    return lhs.as_tie() == rhs.as_tie();
  }
  template<class T, enable<T> =0>
  friend bool operator!=(T const& lhs, T const& rhs) {
    return lhs.as_tie() != rhs.as_tie();
  }
  template<class T, enable<T> =0>
  friend bool operator<(T const& lhs, T const& rhs) {
    return lhs.as_tie() < rhs.as_tie();
  }
  template<class T, enable<T> =0>
  friend bool operator<=(T const& lhs, T const& rhs) {
    return lhs.as_tie() <= rhs.as_tie();
  }
  template<class T, enable<T> =0>
  friend bool operator>=(T const& lhs, T const& rhs) {
    return lhs.as_tie() >= rhs.as_tie();
  }
  template<class T, enable<T> =0>
  friend bool operator>(T const& lhs, T const& rhs) {
    return lhs.as_tie() > rhs.as_tie();
  }
};

which gives us:

struct SomeType:as_tie_ordering {
  int Number;
  std::string Chicken;

  auto as_tie() { return std::tie(Number, Chicken); }
  auto as_tie() const { return std::tie(Number, Chicken); }
};

and now

SomeTime a,b;
bool same = (a==b);

works. Note that as_tie_ordering doesn't use CRTP and is an empty stateless class; this technique uses Koenig lookup to let instances find the operators.

You can also implement an ADL-based get

struct as_tie_get {
  template<class T>
  using enable = std::enable_if_t< std::is_base_of<as_tie_get, std::decay_t<T>>, int>;

  template<std::size_t I, class T,
    enable<T> =0
  >
  friend decltype(auto) get( T&& t ) {
    using std::get;
    return get<I>( std::forward<T>(t).as_tie() );
  }
};

Getting std::tuple_size to work isn't as easy, sadly.

The enable<T> =0 clauses above should be replaced with class=enable<T> in MSVC, as their compiler is not C++11 compliant.

You'll note above I use tuple; but I'm using it generically. I convert my type to a tuple, then use tuple's < to write my <. That glue code deals with tie as a generic bundle of types. That is what tuple is for.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • What's `use_tie_for_operations`? – T.C. Jan 17 '17 at 22:35
  • @T.C. It is a class that brings the `==` operations into ADL lookup territory, thus auto-implementing `==`, `!=`, `<`, `<=`, `>=` and `>` for you with one line of code per class you need it in, in an alternate universe before I renamed the type to `as_tie_ordering`. – Yakk - Adam Nevraumont Jan 17 '17 at 22:47