aliasing a std template function
Asked Answered
C

4

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?

Closemouthed answered 15/1, 2017 at 10:57 Comment(12)
"so is their a way to get around this using only using keyword?" No.Boozer
You can not use simultaneously c++11,c++14,c++1z, be specific․Gadgetry
is there any proposal than to change this?Closemouthed
Is this what you are looking for? If so, I'll put it in an answer. It isn't with using, instead it uses a constexpr variable.Chlodwig
not exactly. but I think I will use it if I don't find sth else very soon. it's mentioned in a comment in [#24330800.Closemouthed
@chedynajjar Not the same. In C++17 you have constexpr lambdas. Much better in your case. See my answer.Chlodwig
@skypjack, ok, true.Closemouthed
If you find yourself giving names to components of a tuple, perhaps it's a good time to re-evaluate good old structs.Spadix
@n.m. I didn't want to write 11 classes, so I thought it will be faster to alias tuples.Closemouthed
So now you are writing more aliases than there are classes you have avoided writing, you need to ask around how to write them, and your code is still less readable than with plain structs.Spadix
@n.m. writing classes which just has only getter and setter is (IMO) longer than using aliases.Closemouthed
Here we go again. You don't write classes with getters and setters for each data member. You write a struct.Spadix
C
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.

Chlodwig answered 15/1, 2017 at 11:14 Comment(6)
This is a generic lambda. A templated function would work just as wellRest
@n.m. C++17 is somehow intriguing. :-)Chlodwig
For extra fun, make it a variable template, and you get something really close in syntax to OP's version, just with using spelled constexpr auto instead.Miscall
@Miscall Nice catch. I'm adding it to the answer (with credits).Chlodwig
I was more thinking about naming the variable template get, and then you can do constexpr auto getNumber = get<0>; constexpr auto getString = get<1>; etc.Miscall
@Miscall Oh, OK. Well, I'll leave anyway also the other solution. It's really a close alias for std::get now. Thank you!! :-)Chlodwig
U
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
Uhlan answered 15/1, 2017 at 12:10 Comment(6)
I think you should delete template<int> struct S {}; or did you put it on purpose?Closemouthed
@chedynajjar ah yes, I took skypjack's answer as a crib.Uhlan
@chedynajjar second approach now fully DRYUhlan
very clean solution considering the lack of aliasing function. I would prefer getting some feature filling this gag, maybe introducing a new keyword to alias only function would be cool.Closemouthed
@RichardHodges You have to pay fees for the crib!! :-)Chlodwig
@RichardHodges Ahahahah. Just joking. Paid back, it wasn't necessary. ;-)Chlodwig
A
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.

Armijo answered 17/1, 2017 at 21:38 Comment(2)
What's use_tie_for_operations?Miscall
@Miscall 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.Armijo
K
0

if it is for readability (as stated in the 1st line of the question) how about this:
say there´s a template function to be aliased:

namespace foo {
  template<class T,int N> T multiply( T );
  template<int N> std::string multiply( const char* val )
  { std::stringstream res; res << val;
    for(int i=1; i<N; ++i) res << " " <<val;
    return res.str();
  }
  template<int N> double multiply( double val)
  { return val * N; }
} 

you write a little wrapper having a template operator()

template<int N> struct WrapFooMultiply {
  template<class V>
  auto operator()( V&& val)const
    -> decltype(foo::multiply<N>(val))
  { return foo::multiply<N>( std::forward<V>(val) ); }
};

and then you use it like this:

WrapFooMultiply<3> x3;
WrapFooMultiply<4> x4;
std::cout << "Santa says: "<< x3("HO!")
  << " answer="<<x4(10.5) ;
Knowitall answered 16/5 at 21:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.