简体   繁体   中英

Compile-time operator[]

How can I achieve a compile-time index operation wrapped in an operator like () or []?

// works, I already made this
template<int i>
constexpr auto get() const
{
    // implementation
    // where i is used as a template parameter to other things
}

// no idea how to achieve this
template</*magic*/>
constexpr auto operator[](/*more magic*/) const
{
    return get</*use magic*/>();
}

Usage

constexpr my_class x;
...= x.get<1>(); // works, kind of ugly
...= x[1]; // doesn't work, parameters aren't compiletime or something

Here's an example I slapped together. Hopefully the solution to this example will be the same solution to my real problem.

#include <tuple>

class c
{
    std::tuple< int, float, char > tuple { 1, 2.f, 'c' };

public:
    template< std::size_t i >
    constexpr auto & get()
    {
        return std::get<i>(tuple);
    }
    //constexpr auto & operator[](std::size_t i)
    //{
    //    return std::get<i>(tuple);
    //}
};

int main()
{
    constexpr c x;
    static_assert( x.get<2>() == 'c' );
    static_assert( x.get<1>() - 2.f < .1f );
    static_assert( x.get<0>() == 1 );
    //static_assert( x[2] == 'c' );
    //static_assert( x[1] - 2.f < .1f );
    //static_assert( x[0] == 1 );
}

Your operator[] must always return the same type for a given parameter type. The way to work around this is to make each parameter a different type.

For example:

template <std::size_t I>
using IndexConstantT = std::integral_constant<std::size_t, I>;

template <std::size_t I>
constexpr IndexConstantT<I> IndexConstant;

class c
{
    std::tuple< int, float, char > tuple { 1, 2.f, 'c' };

public:
    template <std::size_t i>
    constexpr auto& operator[](IndexConstantT<i>) const
    {
        return std::get<i>(tuple);
    }
};

int main()
{
    constexpr const c x;
    static_assert( x[IndexConstant<2>] == 'c' );
    static_assert( x[IndexConstant<1>] - 2.f < .1f );
    static_assert( x[IndexConstant<0>] == 1 );
}

Live Demo


As suggested by @NicolBolas in the comments, to make the syntax a little nicer, you could use a User Defined Literal so that you can use just 2_ic instead of IndexConstant<2> :

constexpr std::size_t c_to_i(char c)
{
    return c - '0';
}

constexpr std::size_t constexpr_pow(std::size_t base, std::size_t exp)
{
    std::size_t ret = 1;
    for (std::size_t i = 0; i < exp; ++i) {
        ret *= base;
    }
    return ret;
}

template <char... Cs, std::size_t... Is>
constexpr std::size_t to_size_t_impl(std::index_sequence<Is...>)
{
    return ((c_to_i(Cs) * constexpr_pow(10, sizeof...(Is) - 1 - Is)) + ...);
}

template <char... Cs>
constexpr std::size_t to_size_t()
{
    return to_size_t_impl<Cs...>(std::make_index_sequence<sizeof...(Cs)>{});
}

template <char... Cs>
constexpr auto operator""_ic()
{
    return IndexConstant<to_size_t<Cs...>()>;
}

Live Demo

operator[] must always return the same type for a given overload. The only way one overload is selected over another is via parameter types, so in this case as the parameter type is always an integer, there is only one overload. Ambigious overloads are not permitted.

My method relies on fully constexpr data and plays around with the return type instead of the operator[] . I've done this within a struct, with static constexpr data members.

Templated versions will stamp out a function for each type presented as a parameter.


Yes you can use this code to get a tuple value at a specific index but your use cases are narrowed to within the context that is shown in the example below.

This is how its used:

struct test
{
    static constexpr array_operator_tuple<int, bool, unsigned> vals{ 1, true, 10u };
    //
    static constexpr auto r0 = vals[0];
    //
    // Equality operator
    static_assert( r0 == 2, "The item required is not valid or not active." );       // Error as expected.
    static_assert( r0 == 1, "The item required is not valid or not active." );       // No error as expected.
    static_assert( r0 == true, "The item required is not valid or not active." );    // Error as expected.
    static_assert( r0 == false, "The item required is not valid or not active." );   // Error as expected.
    static_assert( r0 == 2u, "The item required is not valid or not active." );      // Error as expected.
    static_assert( r0 == 10u, "The item required is not valid or not active." );     // Error as expected.
    //
    // Invalidity operator.
    static_assert( r0 > 10u, "The item required is not valid or not active." );      // Error as expected.
    static_assert( r0 <= 1u, "The item required is not valid or not active." );     // No error as expected.
    static_assert( r0 < 9u, "The item required is not valid or not active." );       // No error as expected.
};

Code can be played with here .

#include <tuple>
#include <iostream>

template <typename T>
struct magic_item
{
    T const * value = nullptr;
    //
    constexpr magic_item(T const * ptr) noexcept : value{ptr} {}
    //
    constexpr bool is_active() const noexcept
    {
        return value != nullptr;
    }
    constexpr bool is_value( T const & v ) const noexcept
    {
        return *value == v;
    }
};

template <typename ... Args>
struct magic_tuple : std::tuple<magic_item<Args>...>
{
    static constexpr size_t count = sizeof...(Args);
    //
    constexpr magic_tuple(Args const * ... args) noexcept : 
        std::tuple<magic_item<Args>...>{ {args}... }
    {}
private:
    template <size_t ... I>
    constexpr bool active_index_impl(std::index_sequence<I...>) const noexcept
    {
        size_t output = ~static_cast<size_t>(0);
        (((std::get<I>(*this) != nullptr) and (output = I, true)) or ...);
        return output;
    }
    //
    template <size_t ... I>
    constexpr bool is_active_impl(size_t index, std::index_sequence<I...>) const noexcept
    {
        return (((index == I) and std::get<I>(*this).is_active()) or ...);
    }
public:
    constexpr bool is_active(size_t index) const noexcept
    {
        return is_active_impl(index, std::make_index_sequence<count>());
    }
    constexpr size_t active_index() const noexcept
    {
        return active_index_impl(std::make_index_sequence<count>());
    }
    //
    template <typename T>
    constexpr bool operator == (T const & value) const noexcept
    {
        using type = std::remove_cv_t<std::decay_t<T>>;
        return std::get<magic_item<type>>(*this).is_active() and (*std::get<magic_item<type>>(*this).value == value);
    }
    template <typename T>
    constexpr bool operator <= (T const & value) const noexcept
    {
        using type = std::remove_cv_t<std::decay_t<T>>;
        return std::get<magic_item<type>>(*this).is_active() and (*std::get<magic_item<type>>(*this).value <= value);
    }
    template <typename T>
    constexpr bool operator >= (T const & value) const noexcept
    {
        using type = std::remove_cv_t<std::decay_t<T>>;
        return std::get<magic_item<type>>(*this).is_active() and (*std::get<magic_item<type>>(*this).value >= value);
    }
    template <typename T>
    constexpr bool operator < (T const & value) const noexcept
    {
        using type = std::remove_cv_t<std::decay_t<T>>;
        return std::get<magic_item<type>>(*this).is_active() and (*std::get<magic_item<type>>(*this).value < value);
    }
    template <typename T>
    constexpr bool operator > (T const & value) const noexcept
    {
        using type = std::remove_cv_t<std::decay_t<T>>;
        return std::get<magic_item<type>>(*this).is_active() and (*std::get<magic_item<type>>(*this).value > value);
    }
};
//
template <typename ... Args, size_t ... I>
constexpr auto get_impl(size_t index, std::tuple<Args...> const & tup, std::index_sequence<I...>) -> magic_tuple< Args ... >
{
    return magic_tuple< Args ... >{ ((index == I) ? &std::get<I>(tup) : nullptr ) ... };
}
template <typename ... Args>
constexpr auto get(size_t index, std::tuple<Args...> const & tup)
{
    return get_impl(index, tup, std::make_index_sequence<sizeof...(Args)>{} );
}
//
template <typename ... Args>
struct array_operator_tuple : std::tuple<Args...>
{
    using base_t= std::tuple<Args...>;
    using base_t::base_t;
    //
    constexpr auto operator[](size_t index) const noexcept
    {
        return get(index, *this);
    }
};
//
struct test
{
    static constexpr array_operator_tuple<int, bool, unsigned> vals{ 1, true, 10u };
    //
    static constexpr auto r0 = vals[0];
    //
    static_assert( r0 == 2, "The item required is not valid or not active." );       // Error as expected.
    static_assert( r0 == 1, "The item required is not valid or not active." );       // No error as expected.
    static_assert( r0 == true, "The item required is not valid or not active." );    // Error as expected.
    static_assert( r0 == false, "The item required is not valid or not active." );   // Error as expected.
    static_assert( r0 == 2u, "The item required is not valid or not active." );      // Error as expected.
    static_assert( r0 == 10u, "The item required is not valid or not active." );     // Error as expected.
    //
    static constexpr auto r1 = vals[1];
    //
    static_assert( r1 == 2, "The item required is not valid or not active." );       // Error as expected.
    static_assert( r1 == 1, "The item required is not valid or not active." );       // Error as expected.
    static_assert( r1 == true, "The item required is not valid or not active." );    // No error as expected.
    static_assert( r1 == false, "The item required is not valid or not active." );   // Error as expected.
    static_assert( r1 == 2u, "The item required is not valid or not active." );      // Error as expected.
    static_assert( r1 == 10u, "The item required is not valid or not active." );     // Error as expected.
    //
    static constexpr auto r2 = vals[2];
    //
    static_assert( r2 == 2, "The item required is not valid or not active." );       // Error as expected.
    static_assert( r2 == 1, "The item required is not valid or not active." );       // Error as expected.
    static_assert( r2 == true, "The item required is not valid or not active." );    // Error as expected.
    static_assert( r2 == false, "The item required is not valid or not active." );   // Error as expected.
    static_assert( r2 == 2u, "The item required is not valid or not active." );      // Error as expected.
    static_assert( r2 == 10u, "The item required is not valid or not active." );     // No error as expected.
    //
    static_assert( r2 > 10u, "The item required is not valid or not active." );      // Error as expected.
    static_assert( r2 >= 10u, "The item required is not valid or not active." );     // No error as expected.
    static_assert( r2 > 9u, "The item required is not valid or not active." );       // No error as expected.
};
//
int main()
{
    test a{};
    return 0;
}

Its generally not good practice to derive from standard library, this is just demonstrating the algorithm.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM