3

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 );
}
nowi
  • 437
  • 4
  • 13
  • 1
    maybe have a look at the constexpr vector in C++20 for ideas – M.M Apr 02 '20 at 03:06
  • I took a look at constexpr std::array and I followed it exactly yet it seems that for some reason it isn't working. It's almost as if it's occurring after the compilation but before it's finished – nowi Apr 02 '20 at 03:09
  • 1
    It would make for a better question if you posted your attempt that you couldn't quite get to work – M.M Apr 02 '20 at 03:12
  • Sure, please wait – nowi Apr 02 '20 at 03:14
  • https://stackoverflow.com/questions/26582875/constexpr-function-parameters-as-template-arguments I wonder why not just just compute it at runtime? What is the point of `struct s`? Just have `constexpr unsigned compute(index) { return 3 - index; }` and `.. get() { return a[compute(i)]; }` and similar for `operator[]`. – KamilCuk Apr 02 '20 at 03:36
  • @KamilCuk so it's impossible? – nowi Apr 02 '20 at 03:37
  • `constexpr auto& operator[](size_t i) { return a[3-i]; }` is [perfectly fine](http://coliru.stacked-crooked.com/a/e620c32728c898e8) in that example. – Miles Budnek Apr 02 '20 at 03:38
  • @MilesBudnek `struct s` is not something I have access to. This is for wrapping tuples. To be clear, I know exactly the maximum index so is there a way to manually define all the functions for those arguments so that [] will work? – nowi Apr 02 '20 at 03:38
  • Can you provide an example that actually demonstrates your constraint then? I'm having trouble figuring out what you actually need. As-is, you can just index into the array; no template hackery needed. – Miles Budnek Apr 02 '20 at 03:40
  • Edited again @MilesBudnek – nowi Apr 02 '20 at 03:45
  • This is unfortunately currently a limitation of C++ mechanisms. There are proposals to change this: [P1045](https://wg21.link/p1045), but none of them has succeeded so far. – L. F. Apr 02 '20 at 03:53
  • 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 int so there is only one overload. Ambigious overloads are not permitted. – David Ledger Apr 02 '20 at 03:55

2 Answers2

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

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • 1
    Feel free to create a UDL, so that you can have `2_ic` resolve to `IndexConstant<2>`. – Nicol Bolas Apr 02 '20 at 04:08
  • @nowi No, there's no way (at least that I can think of) to get different constructor argument values to resolve to different types. – Miles Budnek Apr 02 '20 at 04:22
  • Okay, so say I want to use strings instead of whatever I'm using now and use a compile-time hashing algorithm that returns an index constant. If this is possible, I'll accept this answer, – nowi Apr 02 '20 at 04:47
  • If you have a string hashing function where you can return different types for different strings then sure. [Example](http://coliru.stacked-crooked.com/a/08442e2bdfadcd08) – Miles Budnek Apr 02 '20 at 05:33
  • Actually, you don't even need that, but the syntax gets a bit uglier. [Example](http://coliru.stacked-crooked.com/a/065c6e06a375b1a4) – Miles Budnek Apr 02 '20 at 05:38
  • I like this approach, a UDL may be less of a compromise than my answer. Even if my answer has the syntax required it does introduce some new constraints. – David Ledger Apr 02 '20 at 05:57
0

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.

David Ledger
  • 2,033
  • 1
  • 12
  • 27