がんばってC++でenumerateをつくる

この記事は初心者C++er Advent Calendar 2017 - Qiitaの19日目です。(遅れてしまいすみません) この記事はC++初心者「が」書いた記事です。変なことが書いてあるかもしれませんがご了承ください。

環境

enumerateをつくる

C++17から構造化束縛という機能が追加されて、このようにtuple(など)を一つ一つに分けて受け取ることができるようになりました。

#include <tuple>
#include <string>
#include <iostream>
int main(){
    auto [a,b] = std::tuple<int,std::string>(0,"Hello");
    std::cout << a << "," << b << std::endl;
}

>0,Hello

これは範囲for文で使用することもできます。

#include <tuple>
#include <vector>
#include <string>
#include <iostream>
int main(){
    std::tuple<int,std::string> ta(0,"Hello");
    std::tuple<int,std::string> tb(1,"Dello");
    std::vector<std::tuple<int,std::string>> vec({ta,tb});
    for(const auto& [i,e] : vec){
        std::cout << i << "," << e << std::endl;
    }
}

>0,Hello
>1,Dello

ところでpythonにはenumerateという中身とindexをzipして返す関数があります。

vec = [1,2,3]
for i,e in enumerate(vec):
    print(i,e)
>0 1
>1 2
>2 3

今日はできるだけオーバーヘッドを減らしつつc++でenumerateを実装していきたいと思います。

std::vector<int> vec = {1,2,3};
for(const auto& [i,e] : enumerate(vec)){
    std::cout << i << "," << e;
}

>0,1
>1,2
>2,3

対象

範囲for文で回せるものなら大体動けるようにしたいです
範囲for文の中身はc++17の規格書にこう記述されています。

9.5.4 The range-based for statement [stmt.ranged]
1 The range-based for statement
for ( for-range-declaration : for-range-initializer ) statement
is equivalent to
{
auto &&__range = for-range-initializer ;
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin ) {
for-range-declaration = *__begin;
statement
}
}
where
(1.1) — if the for-range-initializer is an expression, it is regarded as if it were surrounded by parentheses (so
that a comma operator cannot be reinterpreted as delimiting two init-declarators);
(1.2) — __range, __begin, and __end are variables defined for exposition only; and
(1.3) — begin-expr and end-expr are determined as follows:
(1.3.1) — if the for-range-initializer is an expression of array type R, begin-expr and end-expr are __range
and __range + __bound, respectively, where __bound is the array bound. If R is an array of
unknown bound or an array of incomplete type, the program is ill-formed;
(1.3.2) — if the for-range-initializer is an expression of class type C, the unqualified-ids begin and end are
looked up in the scope of C as if by class member access lookup (6.4.5), and if either (or both)
finds at least one declaration, begin-expr and end-expr are __range.begin() and __range.end(),
respectively;
(1.3.3) — otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where
begin and end are looked up in the associated namespaces (6.4.2). [ Note: Ordinary unqualified
lookup (6.4.1) is not performed. — end note ]
[Example:
int array[5] = { 1, 2, 3, 4, 5 };
for (int& x : array)
x *= 2;
— end example ]
2 In the decl-specifier-seq of a for-range-declaration, each decl-specifier shall be either a type-specifier or
constexpr. The decl-specifier-seq shall not define a class or enumeration.
{
    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

このコードから読み取るに、 for-range-initializerに対して使っていいのは

  • begin_expr?
  • end_expr?
  • beginに対して++演算子が使える
  • beginとendの!=演算子が使える

かな…?
begin_expr,end_exprについては以下のQAに載っていました

c++ - 書き方 - vector range based for loop - 解決方法

²begin / endメソッド、またはADLのみのフリー関数begin / end検索、 または Cスタイルの配列サポートのための魔法を呼び出しbegin 。 range_expressionがnamespace stdの型のオブジェクトを返すか、同じものに依存しない限り、 std::beginは呼び出されないことに注意してください。

したがって、範囲for文に突っ込める条件は、 ①. 配列ならrange / (range + __bound)
②. 対応するメンバを持っているならならrange.begin() / range.end()
③. それ以外ならbegin(range) / end(range)
みたい?

規格書の2番は for-range-declarationについての話なので取り敢えず関係ないみたいです

で、C++にはstd::begin/std::endというのがあって、

std::begin, std::cbegin - cppreference.com

これを使えば中身がクラスの場合(②)はbegin(),配列の場合(①)は配列の先頭を受け取れるみたいです。また、

faithandbrave.hateblo.jp

にかかれているように

    using std::begin;
    using std::end;

    auto first = begin(r);
    auto last = end(r);

のようにusingを使って、beginを呼ぶときに名前空間をつけずに呼ぶことで、ADL

Argument Dependent Lookup | 闇夜のC++

を使ってrと関連する名前空間からbeginを取ってこれるみたいなので、③にも対応できそうです。
要するにstd::begin/std::endに入りうる任意の型を受け取れそうです。

とりあえず受け取ったものをtupleに格納して返す単純なenumerateを書いてみます。

#include <tuple>
#include <vector>
#include <string>
#include <iostream>

template <class Container>
auto enumerate(Container& container){
    using std::begin;
    using std::end;
    typedef decltype(*begin(container)) T; // <- Tはbeginから返ってくるイテレーターの中身
    std::vector<std::tuple<size_t, T>> tup_container; // <- こいつに全部いれる
    auto first = begin(container);
    auto last = end(container);
    size_t cnt = 0;
    for(; first != last; ++first){
        tup_container.emplace_back(std::tuple<size_t, T>(++cnt, *first)); // <- いれる
    }
    return tup_container;
}

int main(){
    std::vector<int> vec = {1,2,3};

    for(auto [i,e] : enumerate(vec)){
        ++e;
        std::cout << i << "," << e << std::endl;
    }
    for(auto e : vec){
        std::cout << e << std::endl;
    }
}

>1,2
>2,3
>3,4
>2
>3
>4

auto&ではなくautoと書いているので中身はコピーして欲しいけど参照してますね…これはあとでどうにかします

vector分のメモリを喰ってしまってるので、クラスにして毎回tupleを作って返すようにします

#include <tuple>
#include <vector>
#include <string>
#include <iostream>

template <class Container>
class enumerateImpl{
private:
    static auto getBeginT(){
        std::remove_reference_t<Container> c;
        using std::begin;
        return begin(c);
    }
    static auto getEndT(){
        std::remove_reference_t<Container> c;
        using std::end;
        return end(c);
    }
    Container container; // <- 引数がlvalueの場合は必要ありませんが、
                         //    引数がrvalueの場合、enumerateImplのコンストラクタが終わったときに
                         //    引数のcontainer_のデストラクターが呼ばれてしまうので、
                         //    こいつにムーブさせてます。
                         //    上手くやれば右辺地参照のまま保持できるのかな…?
    typedef decltype(*getBeginT()) T; // <- usingがクラス宣言の中では使えなかったので、
    decltype(getBeginT()) begin_itr;  // <- 型を推測するようの関数を使ってどうにかしました。
    decltype(getEndT()) end_itr;      // <- もっと綺麗な方法がありそう…!
    size_t cnt = 0;
public:
    enumerateImpl(Container&& container_)
    :container(std::forward<Container>(container_))
    {
        using std::begin;
        using std::end;
        begin_itr = begin(container);
        end_itr = end(container);
    }
    enumerateImpl() = delete;
    ~enumerateImpl() = default;
    enumerateImpl(const enumerateImpl& e) // <- begin/endがthisを返してしまっているので必要
    :container(e.container)
    {
        begin_itr = e.begin_itr;
        end_itr = e.end_itr;
    }
    enumerateImpl(enumerateImpl&& p) = delete;
    enumerateImpl& operator=(const enumerateImpl& p) = delete;
    enumerateImpl& operator=(enumerateImpl&& p) = delete;
    enumerateImpl& begin(){
        return *this;
    }
    enumerateImpl& end(){
        return *this;
    }
    std::tuple<size_t, T> operator*(){
        return std::tuple<size_t, T>(cnt, *begin_itr); // <- 毎回作って返す
    }
    enumerateImpl& operator++(){
        ++cnt;
        ++begin_itr;
        return *this;
    }
    bool operator!=(const enumerateImpl&) const{
        return begin_itr != end_itr;
    }
};

template <class Container>
auto enumerate(Container&& container){ // <- enumerate()の使用時にテンプレートの型をいちいち
                                       // <- 書かなくていいように、関数を介す。
    return enumerateImpl<Container>(std::forward<Container>(container));
}

int main(){
    std::vector<int> vec = {1,2,3};
    for(auto [i,e] : enumerate(vec)){ // <- lvalue
        std::cout << i << "," << e << std::endl;
    }
    std::cout << std::endl;
    for(auto [i,e] : enumerate(std::vector<int>({1,2,3}))){ // <- rvalue
        std::cout << i << "," << e << std::endl;
    }
}

>0,1
>1,2
>2,3

>0,1
>1,2
>2,3

イテレーターを作るのは面倒そうなので使う機能だけむりやりclassに入れてみましたが、
begin/endで返したthisをコピーしてしまってるみたいなのでrvalueの時に無駄にコピーしてしまってるので、

{
    auto && __range = range_expression ;
    auto __begin = begin_expr ;               // <- ここでコピーしてる
    auto __end = end_expr ;                   // <- 〃
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

やっぱりイテレーター(のようななにか)を作ります

#include <tuple>
#include <vector>
#include <string>
#include <iostream>

template <class ContainerIterator>
class enumerateImplIterator{
private:
    ContainerIterator container_itr;
    size_t cnt = 0;
    typedef decltype(*container_itr) T;
public:
    enumerateImplIterator(const ContainerIterator& container_itr_)
    :container_itr(container_itr_){}
    enumerateImplIterator() = delete;
    ~enumerateImplIterator() = default;
    enumerateImplIterator(const enumerateImplIterator& p) = delete;
    enumerateImplIterator(enumerateImplIterator&& p) = delete;
    enumerateImplIterator& operator=(const enumerateImplIterator& p) = delete;
    enumerateImplIterator& operator=(enumerateImplIterator&& p) = delete;
    std::tuple<size_t, T> operator*(){
        return std::tuple<size_t, T>(cnt, *container_itr); // <- 毎回作って返す
    }
    enumerateImplIterator& operator++(){
        ++cnt;
        ++container_itr;
        return *this;
    }
    template<class RhsContainerItrT>
    bool operator!=(const enumerateImplIterator<RhsContainerItrT>& c) const{
        return container_itr != c.container_itr;
    }
};

template <class Container>
class enumerateImpl{
private:
    static auto getBeginT(){
        std::remove_reference_t<Container> c;
        using std::begin;
        return begin(c);
    }
    static auto getEndT(){
        std::remove_reference_t<Container> c;
        using std::end;
        return end(c);
    }
    Container container; // <- 引数がlvalueの場合は必要ありませんが、
                         //    引数がrvalueの場合、enumerateImplのコンストラクタが終わったときに
                         //    引数のcontainer_のデストラクターが呼ばれてしまうので、
                         //    こいつにムーブさせてます。
                         //    上手くやれば右辺地参照のまま保持できるのかな…?
    typedef decltype(*getBeginT()) T; // <- usingがクラス宣言の中では使えなかったので、
    decltype(getBeginT()) begin_itr;  // <- 型を推測するようの関数を使ってどうにかしました。
    decltype(getEndT()) end_itr;      // <- もっと綺麗な方法がありそう…!
    size_t cnt = 0;
public:
    enumerateImpl(Container&& container_)
    :container(std::forward<Container>(container_))
    {
        using std::begin;
        using std::end;
        begin_itr = begin(container);
        end_itr = end(container);
    }
    enumerateImpl() = delete;
    ~enumerateImpl() = default;
    enumerateImpl(const enumerateImpl& e) = delete;
    enumerateImpl(enumerateImpl&& p) = delete;
    enumerateImpl& operator=(const enumerateImpl& p) = delete;
    enumerateImpl& operator=(enumerateImpl&& p) = delete;
    auto begin(){
        return enumerateImplIterator(begin_itr);
    }
    auto end(){
        return enumerateImplIterator(end_itr);
    }
};

template <class Container>
auto enumerate(Container&& container){ // <- enumerate()の使用時にテンプレートの型をいちいち
                                       // <- 書かなくていいように、関数を介す。
    return enumerateImpl<Container>(std::forward<Container>(container));
}

int main(){
    std::vector<int> vec = {1,2,3};
    for(auto [i,e] : enumerate(vec)){ // <- lvalue
        std::cout << i << "," << e << std::endl;
    }
    std::cout << std::endl;
    for(auto [i,e] : enumerate(std::vector<int>({1,2,3}))){ // <- rvalue
        std::cout << i << "," << e << std::endl;
    }
}

>0,1
>1,2
>2,3
>
>0,1
>1,2
>2,3

綺麗になったー

で、最初に言ってたコピーして欲しいけど参照してるってのが↓です

int main(){
    {
        std::vector<int> vec = {1,2,3};
        for(auto [i,e] : enumerate(vec)){ // <- コピーして欲しい
            ++e;
            std::cout << i << "," << e << std::endl;
        }
        for(auto e : vec){ // <- lvalue
            std::cout << e << std::endl;
        }
    }

    std::cout << std::endl;

    {
        std::vector<int> vec = {1,2,3};
        for(auto&& [i,e] : enumerate(vec)){ // <- 参照してほしい
            ++e;
            std::cout << i << "," << e << std::endl;
        }
        for(auto e : vec){ // <- lvalue
            std::cout << e << std::endl;
        }
    }
}

>0,2
>1,3
>2,4
>2
>3
>4
>
>0,2
>1,3
>2,4
>2
>3
>4

これは、↓の部分で

{
    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;         // <- ここ
        loop_statement
    }
}

Iteratorのoperator*がrvalueを返してしまっているのでコピーされてないからみたいです。
なのでIteratorのクラス内にtupleを置いておいてlvalueで返します。
また、lvalueを返したとしても、コンテナの中身ではなくて、コンテナの中身への参照が入ったtupleがコピーされてしまっているので、
enumerateTupleみたいなクラスをつくって、コピーコンストラクタが呼ばれたときに、コンテナの中身もコピーさせます。

Iteratorの方もTupleの方も速度が落ちないようにクラスに一箇所しか格納場所を用意しないので、
コードによっては変な挙動をしてしまいますが、enumerate()内では変な挙動は起きない…という感じです…?
後ほど、変な挙動をするコードは書けないように頑張ってみます。)

#include <tuple>
#include <vector>
#include <string>
#include <iostream>

template <class ContainerContentsRef>
class enumerateImplTuple{
private:
    typedef std::remove_reference_t<ContainerContentsRef> ContainerContents;
    char pool_for_copied_contents[sizeof(ContainerContents)];
                                          // <- contentsのコンストラクタが重かったら困るので、
                                          // <- ここでコンストラクタは呼ばないようにする。
    size_t cnt;
    ContainerContents* contents; // <- 参照だと差し替えができないのでポインタ
public:
    enumerateImplTuple& setData(size_t cnt_, ContainerContentsRef& contents_){
        cnt = cnt_;
        contents = &contents_;
        return *this;
    }
    enumerateImplTuple() = default;
    ~enumerateImplTuple() = default;
    enumerateImplTuple(const enumerateImplTuple& p) // <- コンテナの中身(contents)もコピーする
    :cnt(p.cnt),contents((ContainerContents*)(new(pool_for_copied_contents) ContainerContents(*(p.contents)))){}
    enumerateImplTuple(enumerateImplTuple&& p) = delete;
    enumerateImplTuple& operator=(const enumerateImplTuple& p) = delete;
    enumerateImplTuple& operator=(enumerateImplTuple&& p) = delete;
    template<size_t N>
    auto&& get(){
        if      constexpr (N == 0) return cnt;
        else if constexpr (N == 1) return *contents;
    }
    template<size_t N>
    auto&& get() const{
        if      constexpr (N == 0) return cnt;
        else if constexpr (N == 1) return *contents;
    }
};

namespace std
{
    template<class ContainerContentsRef>
    struct tuple_size<enumerateImplTuple<ContainerContentsRef>> : integral_constant<size_t, 2> {};
    template<class ContainerContentsRef>
    struct tuple_element<0, enumerateImplTuple<ContainerContentsRef>> { using type = size_t; };
    template<class ContainerContentsRef>
    struct tuple_element<1, enumerate::enumerateImplTuple<ContainerContentsRef>> { using type = std::remove_reference_t<ContainerContentsRef>; };
}

template <class ContainerIterator>
class enumerateImplIterator{
private:
    ContainerIterator container_itr;
    size_t cnt = 0;
    typedef decltype(*container_itr) T;
    enumerateImplTuple<T> data;
public:
    enumerateImplIterator(const ContainerIterator& container_itr_)
    :container_itr(container_itr_){}
    enumerateImplIterator() = delete;
    ~enumerateImplIterator() = default;
    enumerateImplIterator(const enumerateImplIterator& p) = delete;
    enumerateImplIterator(enumerateImplIterator&& p) = delete;
    enumerateImplIterator& operator=(const enumerateImplIterator& p) = delete;
    enumerateImplIterator& operator=(enumerateImplIterator&& p) = delete;
    enumerateImplTuple<T>& operator*(){
        return data.setData(cnt, *container_itr); // <- 毎回作って返す
    }
    enumerateImplIterator& operator++(){
        ++cnt;
        ++container_itr;
        return *this;
    }
    template<class RhsContainerItrT>
    bool operator!=(const enumerateImplIterator<RhsContainerItrT>& c) const{
        return container_itr != c.container_itr;
    }
};

template <class Container>
class enumerateImpl{
private:
    static auto getBeginT(){
        std::remove_reference_t<Container> c;
        using std::begin;
        return begin(c);
    }
    static auto getEndT(){
        std::remove_reference_t<Container> c;
        using std::end;
        return end(c);
    }
    Container container; // <- 引数がlvalueの場合は必要ありませんが、
                         //    引数がrvalueの場合、enumerateImplのコンストラクタが終わったときに
                         //    引数のcontainer_のデストラクターが呼ばれてしまうので、
                         //    こいつにムーブさせてます。
                         //    上手くやれば右辺地参照のまま保持できるのかな…?
    typedef decltype(*getBeginT()) T; // <- usingがクラス宣言の中では使えなかったので、
    decltype(getBeginT()) begin_itr;  // <- 型を推測するようの関数を使ってどうにかしました。
    decltype(getEndT()) end_itr;      // <- もっと綺麗な方法がありそう…!
    size_t cnt = 0;
public:
    enumerateImpl(Container&& container_)
    :container(std::forward<Container>(container_))
    {
        using std::begin;
        using std::end;
        begin_itr = begin(container);
        end_itr = end(container);
    }
    enumerateImpl() = delete;
    ~enumerateImpl() = default;
    enumerateImpl(const enumerateImpl& e) = delete;
    enumerateImpl(enumerateImpl&& p) = delete;
    enumerateImpl& operator=(const enumerateImpl& p) = delete;
    enumerateImpl& operator=(enumerateImpl&& p) = delete;
    auto begin(){
        return enumerateImplIterator(begin_itr);
    }
    auto end(){
        return enumerateImplIterator(end_itr);
    }
};

template <class Container>
auto enumerate(Container&& container){ // <- enumerate()の使用時にテンプレートの型をいちいち
                                       // <- 書かなくていいように、関数を介す。
    return enumerateImpl<Container>(std::forward<Container>(container));
}

int main(){
    {
        std::vector<int> vec = {1,2,3};
        for(auto [i,e] : enumerate(vec)){ // <- コピーして欲しい
            ++e;
            std::cout << i << "," << e << std::endl;
        }
        for(auto e : vec){ // <- lvalue
            std::cout << e << std::endl;
        }
    }

    std::cout << std::endl;

    {
        std::vector<int> vec = {1,2,3};
        for(auto&& [i,e] : enumerate(vec)){ // <- 参照してほしい
            ++e;
            std::cout << i << "," << e << std::endl;
        }
        for(auto e : vec){ // <- lvalue
            std::cout << e << std::endl;
        }
    }
}

>0,2
>1,3
>2,4
>1
>2
>3
>
>0,2
>1,3
>2,4
>2
>3
>4

できてる感じですね
(構造化束縛のautoとgetが返す型とgetの返り値のautoとtuple_element::typeの型の関係性(処理の順番)がまだ良くわかってなくて行きあたりばったりな感じで書いてしまったので後で勉強し直します)

テンプレートでなんでも受け取れると思ったのですが

qiita.com

initializer_list(に普段は推定されているbraced-init-list)はできないみたいなので、enumerate()に追加します

template <class Container>
auto enumerate(Container&& container){
    return enumerateImpl<Container>(std::forward<Container>(container));
}

template <class T> // <- 追加
auto enumerateMaker(std::initializer_list<T>&& container){
    return enumerateImpl<std::initializer_list<T>>(std::forward<std::initializer_list<T>>(container));
}

braced-init-listみたいなものが他にもあったら困るので探そうと思ったのですが、braced-init-listで検索してもGoogleさんの調子が悪いみたいでちょっとよく分からなかったです...

一応これで完成です。これでもかなり短めにまとまってて、実際はずっと試行錯誤しててとても疲れました…

このenumerateは、構造化束縛付きのfor文で動かすことのみを考えているのですが、他の状況も考慮しようとするとどうしても速度が遅くなってしまいます。なので別の状況で使用しようとしたときにはコンパイルエラーを出そうとしてみたのですが、完璧な方法は思いつきませんでした… まず

    std::vector<int> vec = {1,2,3};
    auto e = enumerate(vec);

のようにfor文以外にenumerate()を書かれると困るので、

namespace enumerate{

    /* 〜省略〜 */

    template <bool is_valid, class Container>
    auto enumerateMaker(Container&& container){
        return enumerateImpl<Container>(std::forward<Container>(container));
    }

    template <bool is_valid, class T>
    auto enumerateMaker(std::initializer_list<T>&& container){
        return enumerateImpl<std::initializer_list<T>>(std::forward<std::initializer_list<T>>(container));
    }
}

#define enumerate_CONCATENATE_DIRECT(s1, s2) s1##s2                        // <- 文字列の結合
#define enumerate_CONCATENATE(s1, s2) enumerate_CONCATENATE_DIRECT(s1, s2) // <- 〃
#define enumerate(...) enumerate::enumerateMaker<enumerate_CONCATENATE(is_valid_enumerate_u1dcgmi9igq88tpz443af5j25j7nzc7r, __LINE__)>(__VA_ARGS__)
                                                                           // <- is_valid_enumerate_...変数が存在しなければエラーを出す
#define for(...) \
    constexpr bool enumerate_CONCATENATE(is_valid_enumerate_u1dcgmi9igq88tpz443af5j25j7nzc7r, __LINE__) = true; \
    for(__VA_ARGS__)
                                                                           // <- for文の手前にuniqueな変数を定義

forマクロでfor文の手前にユニークな変数(ユニークな変数にするために、現在の行数とランダム文字列32文字を足した)を定義させて、enumerateマクロで変数が存在しなければコンパイル時にエラーが出るようにしました。 これしか思いつかなかった…

もうひとつ、

std::vector<int> vec = {1,2,3};
for(auto& t : enumerate(vec)){
    auto& [i,e] = t;
    std::cout << i << "," << e << std::endl;
}

のように構造化束縛を使わない書き方をされても困るので、

namespace enumerate{

    /* 〜省略〜 */

    template <bool is_valid, class Container>
    auto enumerateMaker(Container&& container){
        static_assert(is_valid, "Can't use enumerate without structured bindings");
        return enumerateImpl<Container>(std::forward<Container>(container));
    }

    template <bool is_valid, class T>
    auto enumerateMaker(std::initializer_list<T>&& container){
        static_assert(is_valid, "Can't use enumerate without structured bindings");
        return enumerateImpl<std::initializer_list<T>>(std::forward<std::initializer_list<T>>(container));
    }

    constexpr bool enumerate_check_allowed_for(const char* s){
        bool has_parentheses = false;
        for(const char* c = s; *c != ':' && *c != '\0'; ++c){
            if(*c == '['){
                return true;
            }
        }
        return false;
    }
}

#define enumerate_CONCATENATE_DIRECT(s1, s2) s1##s2
#define enumerate_CONCATENATE(s1, s2) enumerate_CONCATENATE_DIRECT(s1, s2)
#define enumerate(...) enumerate::enumerateMaker<enumerate_CONCATENATE(is_valid_enumerate_u1dcgmi9igq88tpz443af5j25j7nzc7r, __LINE__)>(__VA_ARGS__)
#define for(...) \
    constexpr bool enumerate_CONCATENATE(is_valid_enumerate_u1dcgmi9igq88tpz443af5j25j7nzc7r, __LINE__) = enumerate::enumerate_check_allowed_for(#__VA_ARGS__); \
    for(__VA_ARGS__)

というようにenumerate_check_allowed_for()関数でfor文を文字列で受け取って、それが構造化束縛になってるかどうかを判断することでエラーが出るようにしました。
つらい

完成コード

#include <tuple>
#include <vector>
#include <string>
#include <iostream>

namespace enumerate{
    template <class ContainerContentsRef>
    class enumerateImplTuple{
    private:
        typedef std::remove_reference_t<ContainerContentsRef> ContainerContents;
        char pool_for_copied_contents[sizeof(ContainerContents)];
        size_t cnt;
        ContainerContents* contents;
    public:
        enumerateImplTuple& setData(size_t cnt_, ContainerContentsRef& contents_){
            cnt = cnt_;
            contents = &contents_;
            return *this;
        }
        enumerateImplTuple() = default;
        ~enumerateImplTuple() = default;
        enumerateImplTuple(const enumerateImplTuple& p)
        :cnt(p.cnt),contents((ContainerContents*)(new(pool_for_copied_contents) ContainerContents(*(p.contents)))){}
        enumerateImplTuple(enumerateImplTuple&& p) = delete;
        enumerateImplTuple& operator=(const enumerateImplTuple& p) = delete;
        enumerateImplTuple& operator=(enumerateImplTuple&& p) = delete;
        template<size_t N>
        auto&& get(){
            if      constexpr (N == 0) return cnt;
            else if constexpr (N == 1) return *contents;
        }
        template<size_t N>
        auto&& get() const{
            if      constexpr (N == 0) return cnt;
            else if constexpr (N == 1) return *contents;
        }
    };

    template <class ContainerIterator>
    class enumerateImplIterator{
    private:
        ContainerIterator container_itr;
        size_t cnt = 0;
        typedef decltype(*container_itr) T;
        enumerateImplTuple<T> data;
    public:
        enumerateImplIterator(const ContainerIterator& container_itr_)
        :container_itr(container_itr_){}
        enumerateImplIterator() = delete;
        ~enumerateImplIterator() = default;
        enumerateImplIterator(const enumerateImplIterator& p) = delete;
        enumerateImplIterator(enumerateImplIterator&& p) = delete;
        enumerateImplIterator& operator=(const enumerateImplIterator& p) = delete;
        enumerateImplIterator& operator=(enumerateImplIterator&& p) = delete;
        enumerateImplTuple<T>& operator*(){
            return data.setData(cnt, *container_itr);
        }
        enumerateImplIterator& operator++(){
            ++cnt;
            ++container_itr;
            return *this;
        }
        template<class RhsContainerItrT>
        bool operator!=(const enumerateImplIterator<RhsContainerItrT>& c) const{
            return container_itr != c.container_itr;
        }
    };

    template <class Container>
    class enumerateImpl{
    private:
        static auto getBeginT(){
            std::remove_reference_t<Container> c;
            using std::begin;
            return begin(c);
        }
        static auto getEndT(){
            std::remove_reference_t<Container> c;
            using std::end;
            return end(c);
        }
        Container container;
        typedef decltype(*getBeginT()) T;
        decltype(getBeginT()) begin_itr;
        decltype(getEndT()) end_itr;
        size_t cnt = 0;
    public:
        enumerateImpl(Container&& container_)
        :container(std::forward<Container>(container_))
        {
            using std::begin;
            using std::end;
            begin_itr = begin(container);
            end_itr = end(container);
        }
        enumerateImpl() = delete;
        ~enumerateImpl() = default;
        enumerateImpl(const enumerateImpl& e) = delete;
        enumerateImpl(enumerateImpl&& p) = delete;
        enumerateImpl& operator=(const enumerateImpl& p) = delete;
        enumerateImpl& operator=(enumerateImpl&& p) = delete;
        auto begin(){
            return enumerateImplIterator(begin_itr);
        }
        auto end(){
            return enumerateImplIterator(end_itr);
        }
    };

    template <bool is_valid, class Container>
    auto enumerateMaker(Container&& container){
        static_assert(is_valid, "Can't use enumerate without structured bindings");
        return enumerateImpl<Container>(std::forward<Container>(container));
    }

    template <bool is_valid, class T>
    auto enumerateMaker(std::initializer_list<T>&& container){
        static_assert(is_valid, "Can't use enumerate without structured bindings");
        return enumerateImpl<std::initializer_list<T>>(std::forward<std::initializer_list<T>>(container));
    }

    constexpr bool enumerate_check_allowed_for(const char* s){
        bool has_parentheses = false;
        for(const char* c = s; *c != ':' && *c != '\0'; ++c){
            if(*c == '['){
                return true;
            }
        }
        return false;
    }
}

namespace std
{
    template<class ContainerContentsRef>
    struct tuple_size<enumerate::enumerateImplTuple<ContainerContentsRef>> : integral_constant<size_t, 2> {};
    template<class ContainerContentsRef>
    struct tuple_element<0, enumerate::enumerateImplTuple<ContainerContentsRef>> { using type = size_t; };
    template<class ContainerContentsRef>
    struct tuple_element<1, enumerate::enumerateImplTuple<ContainerContentsRef>> { using type = std::remove_reference_t<ContainerContentsRef>; };
}

#define enumerate_CONCATENATE_DIRECT(s1, s2) s1##s2
#define enumerate_CONCATENATE(s1, s2) enumerate_CONCATENATE_DIRECT(s1, s2)
#define enumerate(...) enumerate::enumerateMaker<enumerate_CONCATENATE(is_valid_enumerate_u1dcgmi9igq88tpz443af5j25j7nzc7r, __LINE__)>(__VA_ARGS__)
#define for(...) \
    constexpr bool enumerate_CONCATENATE(is_valid_enumerate_u1dcgmi9igq88tpz443af5j25j7nzc7r, __LINE__) = enumerate::enumerate_check_allowed_for(#__VA_ARGS__); \
    for(__VA_ARGS__)

ベンチマーク

|                           | 普通 | enumerate |   |   |  
|---------------------------|------|-----------|---|---|  
| 2億個×5回:参照          | 1.7s | 1.7s      |   |   |  
| 2億個×5回:コピー        | 1.7s | 1.9s      |   |   |  
| 10個×3億回:参照         | 2.3s | 2.3s      |   |   |  
| 10個×3億回:コピー       | 3.1s | 3.9s      |   |   |  
| rvalue 10個×3億回:参照  | 1.3s | 1.3s      |   |   |  
| rvalue 10個×3億回:コピー| 1.3s | 3.4s      |   |   |  
|                           |      |           |   |   |  

コピーの場合は重いですか参照の場合は同じ速度で動いてそうです
(最適化されてるかもしれない)