user6256186
user6256186

Reputation: 113

Different constexpr behavior vs2015u2 vs gcc

I have a std::tuple filled with objects instantiated from class template with one type parameter. Now I want to get, at compile time, an element with specified type parameter from my tuple. Here is the code:

template<typename Params, typename Descriptor>
struct IsParamsEqual;

template<typename Params1, typename Params2, ApiCommand::Value value>
struct IsParamsEqual<Params1, Descriptor<value, Params2>>
{
    static constexpr bool v = std::is_same<Params1, Params2>::value;
};

template<typename Params, size_t I, typename... Args>
constexpr size_t getIndexByParamsHelper(const IndexSequence<I>&, const std::tuple<Args...> &)
{
    return I;
}

template<typename Params, size_t I, size_t... Indexes, typename... Args>
constexpr size_t getIndexByParamsHelper(const IndexSequence<I, Indexes...> &, 
                                        const std::tuple<Args...> &tuple)
{
    return IsParamsEqual<Params, typename std::tuple_element<I, std::tuple<Args...>>::type>::v ?
           I : getIndexByParamsHelper<Params>(IndexSequence<Indexes...>(), tuple);
}

template<typename Params, size_t... Indexes, typename... Args>
constexpr size_t getIndexByParams(const IndexSequence<Indexes...> &seq, 
                                  const std::tuple<Args...> &tuple)
{
    return getIndexByParamsHelper<Params>(seq, tuple);
}

template<typename Params, typename... Args>
constexpr auto getByParamsImpl(const std::tuple<Args...> &tuple)
{
    constexpr size_t I = getIndexByParams<Params>(
        typename MakeIndexSequence<sizeof...(Args)>::type(), tuple);
    static_assert(std::is_same<typename std::remove_reference<decltype(
         std::get<I>(tuple))>::type::paramType, Params>::value,
         "Param not found");
    return std::get<I>(tuple);
}

This compiles fine on gcc 4.8.4 but not on vs2015u2. The error is in the getByParamsImpl() and it says:

error C2131: expression did not evaluate to a constant
note: failure was caused by non-constant arguments or reference to a non-constant symbol
see usage of 'I'

Obviously, the compiler thinks that getIndexByParams() return value is not constexpr.

Why, and - more importantly - how can this be fixed?

Upvotes: 1

Views: 151

Answers (2)

user6256186
user6256186

Reputation: 113

Since I can't just throw away 'bad' compiler, I've implemented workaround. Maybe it will help someone.

template<typename Params, typename Descriptor>
struct IsParamsEqual;

template<typename Params1, typename Params2, ApiCommand::Value value>
struct IsParamsEqual<Params1, Descriptor<value, Params2>>
{
    static constexpr bool v = std::is_same<Params1, Params2>::value;
};

template<typename Params, typename IS, typename... Args>
struct GetIndexByParam;

template<typename Param, typename... Args>
struct GetIndexByParam<Param, IndexSequence<>, Args...>
{
    typedef typename std::integral_constant<size_t, 0> type;
};

template <typename Param, size_t I, size_t... IndexesTail,  typename Head, 
    typename ...Tail>
struct GetIndexByParam<Param, IndexSequence<I, IndexesTail...>, Head, Tail...>
{
    typedef typename std::conditional<IsParamsEqual<Param, Head>::v,
        std::integral_constant<size_t, I>,
        typename GetIndexByParam<Param, IndexSequence<IndexesTail...>, Tail...>::type >::type type;
};

template<typename Params, typename... Args>
constexpr auto getByParamsImpl(const std::tuple<Args...> &tuple)
{
    typedef typename GetIndexByParam<Params,        
        typename MakeIndexSequence<sizeof...(Args)>::type, Args...>::type IndexValueType;
    static_assert(std::is_same<
        typename std::remove_reference<decltype(std::get<IndexValueType::value>(tuple))>::type::paramType, \
            Params>::value, "Parameter not found");
    return std::get<IndexValueType::value>(tuple);
}

Upvotes: 0

Pixelchemist
Pixelchemist

Reputation: 24956

If I get it right you're trying to do something like

constexpr std::tuple<int, float, int> t1{ 8, 2.0f, 1 };
constexpr std::tuple<int, float, double> t2{ 1, 2.0f, 3.0 };
constexpr std::tuple<unsigned, float, double> t3{ 1u, 2.0f, 3.0 };

constexpr auto f1 = find_by_type<int>(t1);
constexpr auto f2 = find_by_type<double>(t2);
constexpr auto f3 = find_by_type<unsigned>(t3);

where

  • f1 should be equivalent to constexpr int f1 = 8;
  • f2 should be equivalent to constexpr double f2 = 3.0;
  • f3 should be equivalent to constexpr unsigned f3 = 1u;

You can get something like this via recursion through the paramter pack:

// helper
template<class ... Ts> struct Finder;
// empty to have compiler error if type is not found
template<class T> struct Finder<T> {  };

// if first type in pack is type to find, we stop
template<class ToFind, class ... Ts>
struct Finder<ToFind, ToFind, Ts...>
{
  constexpr static std::size_t N = 0u;
};

// if first type in pack is not type to find
// we increase by one and go down the pack
template<class ToFind, class First, class ... Amongst>
struct Finder<ToFind, First, Amongst...>
{
  constexpr static std::size_t N = 1u + Finder<ToFind, Amongst...>::N;
};

template<class P, class ... Args>
constexpr auto find_by_type(const std::tuple<Args...> &t)
{
  return std::get<Finder<P, Args...>::N>(t);
}

Upvotes: 0

Related Questions