lundi 29 août 2016

How to increment an enum inside a namespace

I have an enum like this:

namespace api {
    enum Operation {
        INSERT =1,
        UPDATE =2,
        DELETE =3,
    };

    const Operation Operation_MIN = INSERT;
    const Operation Operation_MAX = DELETE;
} // api

and I want to write some tests that use it:

#include "gtest.h"
class FWTest : public ::testing::Test {};

class FWTestPar : public FWTest, public ::testing::WithParamInterface<api::Operation>
{};

INSTANTIATE_TEST_CASE_P(Piping, FWTestPar, ::testing::Range(
    api::Operation_MIN, api::Operation_MAX+1)
);

To be able to increment enums, I wrote this operator:

template <typename E>
constexpr typename std::enable_if<std::is_enum<E>::value, E>::type
operator+(E val, int step)
{
    return static_cast<E>(static_cast<int>(val) + step);
}

This compiles fine with G++ from 4.8 to 6.2, but clang refuses to find operator+ :

clang++ -g -std=c++14 -Wall -pedantic -Wextra -Wformat=2 -I ~/workspace/zarquon -o "enum_inc_real" "enum_inc_real.cc" (in directory: /tmp)
In file included from enum_inc_real.cc:1:
third_party/gmock/gtest/gtest.h:10250:34: error: assigning to 'api::Operation' from incompatible type 'int'
    for (T i = begin; i < end; i = i + step)
                                 ^ ~~~~~~~~
third_party/gmock/gtest/gtest.h:10191:33: note: in instantiation of member function 'testing::internal::RangeGenerator<api::Operation, int>::CalculateEndIndex' requested here
        step_(step), end_index_(CalculateEndIndex(begin, end, step)) {}
                                ^
third_party/gmock/gtest/gtest.h:15815:11: note: in instantiation of member function 'testing::internal::RangeGenerator<api::Operation, int>::RangeGenerator' requested here
      new internal::RangeGenerator<T, IncrementT>(start, end, step));
          ^
third_party/gmock/gtest/gtest.h:15820:10: note: in instantiation of function template specialization 'testing::Range<api::Operation, int>' requested here
  return Range(start, end, 1);
         ^
enum_inc_real.cc:34:55: note: in instantiation of function template specialization 'testing::Range<api::Operation>' requested here
INSTANTIATE_TEST_CASE_P(Piping, FWTestPar, ::testing::Range(
                                                      ^
In file included from enum_inc_real.cc:1:
third_party/gmock/gtest/gtest.h:10213:14: error: assigning to 'api::Operation' from incompatible type 'int'
      value_ = value_ + step_;
             ^ ~~~~~~~~~~~~~~
third_party/gmock/gtest/gtest.h:10204:5: note: in instantiation of member function 'testing::internal::RangeGenerator<api::Operation, int>::Iterator::Advance' requested here
    Iterator(const ParamGeneratorInterface<T>* base, T value, int index,
    ^
third_party/gmock/gtest/gtest.h:10195:16: note: in instantiation of member function 'testing::internal::RangeGenerator<api::Operation, int>::Iterator::Iterator' requested here
    return new Iterator(this, begin_, 0, step_);
               ^
third_party/gmock/gtest/gtest.h:10189:3: note: in instantiation of member function 'testing::internal::RangeGenerator<api::Operation, int>::Begin' requested here
  RangeGenerator(T begin, T end, IncrementT step)
  ^
third_party/gmock/gtest/gtest.h:15815:11: note: in instantiation of member function 'testing::internal::RangeGenerator<api::Operation, int>::RangeGenerator' requested here
      new internal::RangeGenerator<T, IncrementT>(start, end, step));
          ^
third_party/gmock/gtest/gtest.h:15820:10: note: in instantiation of function template specialization 'testing::Range<api::Operation, int>' requested here
  return Range(start, end, 1);
         ^
enum_inc_real.cc:34:55: note: in instantiation of function template specialization 'testing::Range<api::Operation>' requested here
INSTANTIATE_TEST_CASE_P(Piping, FWTestPar, ::testing::Range(
                                                      ^

If the templated operator+ is defined inside the api namespace, then clang++ also accepts this code.

So, which compiler is right?

Do I really have to drag operator+ into every single namespace that contains an enum I want to increment?

The odd thing is that gtest does something similar to this:

namespace testin_ {
    namespace intl {
        template<typename T>
        class PII {};

        template<typename T, typename Inc>
        class RG{
        public:
            RG(T begin, T end, Inc s)
            : begin_{begin}, end_{end}, step_{s}, end_index_{CalculateEndIndex(begin, end, s)}
            {}

            T begin() { return begin_; }
            T end  () { return end_;   }
        private:

        class It : public PII<T> {
            void advance() {
                value_ = value_ + step_;
            }

            T   value_;
            int index_;
            Inc step_;
        };
        static int CalculateEndIndex(const T& begin,
                                     const T& end,
                                     const Inc& step) {
          int end_index = 0;
          for (T i = begin; i < end; i = i + step)
            end_index++;
          return end_index;
        }

            const T begin_;
            const T end_;
            const Inc step_;
            const int end_index_;
        };
    } // intl

    template<typename T, typename Inc>
    intl::RG<T, Inc> Rg(T start, T end, Inc step) {
        return intl::RG<T, Inc>(start, end, step);
    }
}

auto gen = testin_::Rg(api::INSERT, api::DELETE, 1);

bu that is accepted without complaints by clang++

Aucun commentaire:

Enregistrer un commentaire