0

I would like to use enum values as index to access to tuple items. I've read this post to help.
I'm working in VS2013 thus I cannot use C++14 very handy auto without trailing return.
Static cast works but it's cumbersome, also it requires tuple is freely accessed (in real code attributes are in protected scope).
I want to improve this by using a templated method GetAttribute.
I tried this but it produces an error:

prog.cpp:30:119: error: invalid operands of types '' and 'size_t {aka unsigned int}' to binary 'operator<' auto GetAttribute(AttributeName attributeName) -> decltype(std::declval(std::get(attributeName)>(attributes))) ^ prog.cpp:30:119: error: invalid operands of types '' and 'size_t {aka unsigned int}' to binary 'operator<' prog.cpp: In function 'int main()': prog.cpp:57:4: error: 'struct X' has no member named 'GetAttribute' x.GetAttribute(XParameters::PARAMETER1); // Does not compile. ^

Live demo

#include <tuple>
#include <cstddef>
#include <utility>
#include <iostream>

enum class XParameters : unsigned int
{
    PARAMETER1, // int
    PARAMETER2, // float
    MAX,
};

enum class YParameters : unsigned int
{
    PARAMETER3 = XParameters::MAX // std::string
};

using XTuple = std::tuple<int, float>;
using YAttributes = std::tuple<std::string>;
using YTuple = decltype(tuple_cat(XTuple(), YAttributes()));

template <typename Attributes>
struct Common
{
    Common(Attributes&& attr) : attributes(std::move(attr)) {}

    Attributes attributes;

    template <typename AttributeName>
    auto GetAttribute(AttributeName attributeName) -> decltype(std::declval(std::get<static_cast<size_t>(attributeName)>(attributes)))
    {
        return std::get<static_cast<size_t>(attributeName)>(attributes);
    }
};

struct X : Common<XTuple>
{
    X() : Common(std::make_tuple(42, 3.14f)) {}
};

struct Y : Common<YTuple>
{
    Y() : Common(std::make_tuple(666, 0.01f, "string")) {}
};

int main()
{
    X x;
    Y y;

    int parameter1 = std::get<static_cast<size_t>(XParameters::PARAMETER1)>(x.attributes); // Compiles, works.
    std::cout << parameter1 << std::endl;

    std::string parameter3 = std::get<static_cast<size_t>(YParameters::PARAMETER3)>(y.attributes); // Compiles, works.
    std::cout << parameter3 << std::endl;

    // Shorter code
    x.GetAttribute(XParameters::PARAMETER1); // Does not compile.

    //parameter3 = std::get<static_cast<size_t>(YParameters::PARAMETER3)>(x.attributes); // Does not compile, phew...

    return 0;
}
5
  • How should the compiler convert the runtime value attributeName to a compile time value for std::get's template parameter in the static_cast? Commented Feb 17, 2016 at 11:32
  • attributeName value is known at compile time, it's equal to XParameters::PARAMETER1 for x.GetAttribute(XParameters::PARAMETER1); instruction. Commented Feb 17, 2016 at 12:23
  • 1
    It is known at the call site at compile time but it is a normal function call so inside the GetAttribute function it is a runtime parameter. If available you can test that with static_assert(XParameters::PARAMETER1 == attributeName, ""); which should tell you that attributeName is not a compile time value. Commented Feb 17, 2016 at 12:26
  • Do you have a suggestion to fix this ? Commented Feb 17, 2016 at 16:23
  • Btw.: Why do you use declval in the decltype? What is the use of struct Common? (You could achieve the same with a free function and raw std::tuples) Commented Feb 18, 2016 at 12:26

1 Answer 1

2

I am afraid there will be no pretty solution.

Ugly variant - Cast at call site

The easiest way to achieve something similar would be to pass the value as a non-type template parameter:

template <size_t attributeName>
auto GetAttribute() -> decltype(std::get<attributeName>(attributes))
{
    return std::get<attributeName>(attributes);
}

This would make the call ugly because you would have to do the cast there:

x.GetAttribute<static_cast<size_t>(XParameters::PARAMETER1)>();

Not so ugly variant - associated enum type

You could work around that by associating an enum type with the Common class like this:

template <typename Attributes, typename EnumType>
struct Common
{
    Common(Attributes&& attr) : attributes(std::move(attr)) {}

    Attributes attributes;

    template <size_t attributeName>
    auto GetAttribute() -> decltype(std::get<attributeName>(attributes))
    {
        return std::get<attributeName>(attributes);
    }

    template <EnumType attributeName>
    auto GetAttribute() -> decltype(GetAttribute<static_cast<size_t>(attributeName)>())
    {
        return GetAttribute<static_cast<size_t>(attributeName)>();
    }

};

Which would allow for calling like that:

struct X : Common<XTuple, XParameters>
{
    X() : Common(std::make_tuple(42, 3.14f)) {}
};

x.GetAttribute<XParameters::PARAMETER1>();

Obviously, you would need to dedicated an enum for each Common instance.

Best variant(?) - Free (overloaded) function

In this case you just use raw tuples. Then you roll your own function (e.g. GetAttribute) that you overload for your specific type of tuple and enum:

template <XParameters attributeName>
auto GetAttribute(XTuple &tuple) -> decltype(std::get<static_cast<size_t>(attributeName)>(tuple) )
{
    return  std::get<static_cast<size_t>(attributeName)>(tuple);
}

GetAttribute<XParameters::PARAMETER1>(x.attributes);

Defining GetAttribute in this case has a lot of boilerplate code so you might want to hide it behind a macro:

#define DEFINE_GetAttribute(ENUM_TYPE, TUPLE_TYPE)                             \
  template <ENUM_TYPE attributeName>                                           \
  auto GetAttribute(TUPLE_TYPE &tuple)                                         \
      ->decltype(std::get<static_cast<size_t>(attributeName)>(tuple)) {        \
    return std::get<static_cast<size_t>(attributeName)>(tuple);                \
  }

DEFINE_GetAttribute(XParameters, XTuple)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.