From 847f72f38eb4809e61233872f375b1e4c183de13 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Steffen=20M=C3=BCthing?= <muething@dune-project.org>
Date: Tue, 10 Mar 2015 19:51:41 +0100
Subject: [PATCH] [Typetraits][Compatibility] Provide partial backwards
 compatibility for is_indexable on old GCC versions

Older GCC versions have some nasty bugs related to SFINAE and indexing
with operator[]. This patch adds a configuration check for those
problems and a partial backwards compatibility hack for those older
compilers that is sufficient for our current use case in the VTK
writer.
---
 cmake/modules/CheckCXX11Features.cmake |  49 +++++++++++
 config.h.cmake                         |   3 +
 dune/common/typetraits.hh              | 111 +++++++++++++++++++++++--
 m4/CMakeLists.txt                      |   1 +
 m4/Makefile.am                         |   1 +
 m4/cxx11_is_indexable_support.m4       |  64 ++++++++++++++
 m4/dune_common.m4                      |   3 +-
 7 files changed, 226 insertions(+), 6 deletions(-)
 create mode 100644 m4/cxx11_is_indexable_support.m4

diff --git a/cmake/modules/CheckCXX11Features.cmake b/cmake/modules/CheckCXX11Features.cmake
index 611cacf72..85ad3689d 100644
--- a/cmake/modules/CheckCXX11Features.cmake
+++ b/cmake/modules/CheckCXX11Features.cmake
@@ -217,6 +217,55 @@ check_cxx_source_compiles("
 " HAVE_STD_DECLVAL
   )
 
+# full support for is_indexable (checking whether a type supports operator[])
+check_cxx_source_compiles("
+  #include <utility>
+  #include <type_traits>
+  #include <array>
+
+  template <class T>
+  typename std::add_rvalue_reference<T>::type declval();
+
+  namespace detail {
+
+    template<typename T, typename I, typename = int>
+    struct _is_indexable
+      : public std::false_type
+    {};
+
+    template<typename T, typename I>
+    struct _is_indexable<T,I,typename std::enable_if<(sizeof(declval<T>()[declval<I>()]) > 0),int>::type>
+      : public std::true_type
+    {};
+
+  }
+
+  template<typename T, typename I = std::size_t>
+  struct is_indexable
+    : public detail::_is_indexable<T,I>
+  {};
+
+  struct foo_type {};
+
+  int main()
+  {
+    double x;
+    std::array<double,4> y;
+    double z[5];
+    foo_type f;
+
+    static_assert(not is_indexable<decltype(x)>::value,\"scalar type\");
+    static_assert(is_indexable<decltype(y)>::value,\"indexable class\");
+    static_assert(is_indexable<decltype(z)>::value,\"array\");
+    static_assert(not is_indexable<decltype(f)>::value,\"not indexable class\");
+    static_assert(not is_indexable<decltype(y),foo_type>::value,\"custom index type\");
+
+    return 0;
+  }
+" HAVE_IS_INDEXABLE_SUPPORT
+  )
+
+
 cmake_pop_check_state()
 
 # find the threading library
diff --git a/config.h.cmake b/config.h.cmake
index ce6599063..9f6b370c1 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -116,6 +116,9 @@
 /* Define to 1 if C++11 std::declval() is supported */
 #cmakedefine HAVE_STD_DECLVAL 1
 
+/* Define to 1 if the compiler properly supports testing for operator[] */
+#cmakedefine HAVE_IS_INDEXABLE_SUPPORT 1
+
 /* does the compiler support the keyword 'final'? */
 #cmakedefine HAVE_KEYWORD_FINAL 1
 
diff --git a/dune/common/typetraits.hh b/dune/common/typetraits.hh
index 64f745c6e..933a43797 100644
--- a/dune/common/typetraits.hh
+++ b/dune/common/typetraits.hh
@@ -6,6 +6,7 @@
 #include <type_traits>
 
 #include <dune/common/deprecated.hh>
+#include <dune/common/std/utility.hh>
 
 namespace Dune
 {
@@ -404,23 +405,123 @@ namespace Dune
     static const bool value = true;
   };
 
-  namespace {
 
-    template<typename T, typename I, decltype(*(static_cast<T*>(nullptr))[*(static_cast<T*>(nullptr))],0) = 0>
-    auto _is_indexable(T*) -> std::true_type;
+#if defined(DOXYGEN) or HAVE_IS_INDEXABLE_SUPPORT
+
+#ifndef DOXYGEN
+
+  namespace detail {
+
+    template<typename T, typename I, typename = int>
+    struct _is_indexable
+      : public std::false_type
+    {};
 
     template<typename T, typename I>
-    auto _is_indexable(void*) -> std::false_type;
+    struct _is_indexable<T,I,typename std::enable_if<(sizeof(Std::declval<T>()[Std::declval<I>()]) > 0),int>::type>
+      : public std::true_type
+    {};
 
   }
 
+#endif // DOXYGEN
+
   //! Type trait to determine whether an instance of T has an operator[](I), i.e. whether
   //! it can be indexed with an index of type I.
+  /**
+   * \warning Not all compilers support testing for arbitrary index types. In particular, there
+   *          are problems with GCC 4.4 and 4.5.
+   */
   template<typename T, typename I = std::size_t>
   struct is_indexable
-    : public decltype(_is_indexable<T,I>(static_cast<T*>(nullptr)))
+    : public detail::_is_indexable<T,I>
   {};
 
+
+#else // defined(DOXYGEN) or HAVE_IS_INDEXABLE_SUPPORT
+
+
+  // okay, here follows a mess of compiler bug workarounds...
+  // GCC 4.4 dies if we try to subscript a simple type like int and
+  // both GCC 4.4 and 4.5 don't like using arbitrary types as subscripts
+  // for macros.
+  // So we make sure to only ever attempt the SFINAE for operator[] for
+  // class types, and to make sure the compiler doesn't become overly eager
+  // we have to do some lazy evaluation tricks with nested templates and
+  // stuff.
+  // Let's get rid of GCC 4.4 ASAP!
+
+
+  namespace detail {
+
+    // simple wrapper template to support the lazy evaluation required
+    // in _is_indexable
+    template<typename T>
+    struct _lazy
+    {
+      template<typename U>
+      struct evaluate
+      {
+        typedef T type;
+      };
+    };
+
+    // default version, gets picked if SFINAE fails
+    template<typename T, typename = int>
+    struct _is_indexable
+      : public std::false_type
+    {};
+
+    // version for types supporting the subscript operation
+    template<typename T>
+    struct _is_indexable<T,decltype(Std::declval<T>()[0],0)>
+      : public std::true_type
+    {};
+
+    // helper struct for delaying the evaluation until we are sure
+    // that T is a class (i.e. until we are outside std::conditional
+    // below)
+    struct _check_for_index_operator
+    {
+
+      template<typename T>
+      struct evaluate
+        : public _is_indexable<T>
+      {};
+
+    };
+
+  }
+
+  // The rationale here is as follows:
+  // 1) If we have an array, we assume we can index into it. That isn't
+  //    true if I isn't an integral type, but that's why we have the static assertion
+  //    in the body - we could of course try and check whether I is integral, but I
+  //    can't be arsed and want to provide a motivation to switch to a newer compiler...
+  // 2) If we have a class, we use SFINAE to check for operator[]
+  // 3) Otherwise, we assume that T does not support indexing
+  //
+  // In order to make sure that the compiler doesn't accidentally try the SFINAE evaluation
+  // on an array or a scalar, we have to resort to lazy evaluation.
+  template<typename T, typename I = std::size_t>
+  struct is_indexable
+    : public std::conditional<
+               std::is_array<T>::value,
+               detail::_lazy<std::true_type>,
+               typename std::conditional<
+                 std::is_class<T>::value,
+                 detail::_check_for_index_operator,
+                 detail::_lazy<std::false_type>
+                 >::type
+               >::type::template evaluate<T>::type
+  {
+    static_assert(std::is_same<I,std::size_t>::value,"Your compiler is broken and does not support checking for arbitrary index types");
+  };
+
+
+#endif // defined(DOXYGEN) or HAVE_IS_INDEXABLE_SUPPORT
+
+
   /** @} */
 }
 #endif
diff --git a/m4/CMakeLists.txt b/m4/CMakeLists.txt
index e91561816..749751f13 100644
--- a/m4/CMakeLists.txt
+++ b/m4/CMakeLists.txt
@@ -8,6 +8,7 @@ install(PROGRAMS
         ax_lang_compiler_ms.m4
         boost_fusion.m4
         cxx11_compiler.m4
+        cxx11_is_indexable_support.m4
         cxx0x_nullptr.m4
         cxx11_constexpr.m4
         cxx11_final.m4
diff --git a/m4/Makefile.am b/m4/Makefile.am
index a2f87b8d3..ea8865717 100644
--- a/m4/Makefile.am
+++ b/m4/Makefile.am
@@ -14,6 +14,7 @@ ALLM4S = 					\
 	cxx0x_nullptr.m4			\
 	cxx11_constexpr.m4			\
 	cxx11_final.m4				\
+	cxx11_is_indexable_support.m4           \
 	cxx11_noexcept.m4			\
 	cxx11_range_based_for.m4		\
 	cxx11_std_declval.m4                    \
diff --git a/m4/cxx11_is_indexable_support.m4 b/m4/cxx11_is_indexable_support.m4
new file mode 100644
index 000000000..ff5aba8f0
--- /dev/null
+++ b/m4/cxx11_is_indexable_support.m4
@@ -0,0 +1,64 @@
+# tests whether the compiler properly supports testing for operator[]
+# the associated macro is called HAVE_IS_INDEXABLE_SUPPORT
+
+AC_DEFUN([DUNE_CXX11_IS_INDEXABLE_SUPPORT_CHECK],[
+  AC_CACHE_CHECK([whether the compiler properly supports testing for operator[[]]],
+    dune_cv_cxx11_is_indexable_support,
+    [
+    AC_REQUIRE([AC_PROG_CXX])
+    AC_REQUIRE([CXX11])
+    AC_LANG_PUSH([C++])
+    AC_COMPILE_IFELSE([
+      AC_LANG_PROGRAM(
+        [
+          #include <utility>
+          #include <type_traits>
+          #include <array>
+
+          template <class T>
+          typename std::add_rvalue_reference<T>::type declval();
+
+          namespace detail {
+
+            template<typename T, typename I, typename = int>
+            struct _is_indexable
+              : public std::false_type
+            {};
+
+            template<typename T, typename I>
+            struct _is_indexable<T,I,typename std::enable_if<(sizeof(declval<T>()[[declval<I>()]]) > 0),int>::type>
+              : public std::true_type
+            {};
+
+          }
+
+          template<typename T, typename I = std::size_t>
+          struct is_indexable
+            : public detail::_is_indexable<T,I>
+          {};
+
+          struct foo_type {};
+
+        ],[
+          double x;
+          std::array<double,4> y;
+          double z[[5]];
+          foo_type f;
+
+          static_assert(not is_indexable<decltype(x)>::value,"scalar type");
+          static_assert(is_indexable<decltype(y)>::value,"indexable class");
+          static_assert(is_indexable<decltype(z)>::value,"array");
+          static_assert(not is_indexable<decltype(f)>::value,"not indexable class");
+          static_assert(not is_indexable<decltype(y),foo_type>::value,"custom index type");
+
+          return 0;
+        ])],
+      dune_cv_cxx11_is_indexable_support=yes,
+      dune_cv_cxx11_is_indexable_support=no)
+    AC_LANG_POP
+  ])
+  if test "$dune_cv_cxx11_is_indexable_support" = yes; then
+    AC_DEFINE(HAVE_IS_INDEXABLE_SUPPORT, 1,
+      [Define to 1 if the compiler properly supports testing for operator[[]]])
+  fi
+])
diff --git a/m4/dune_common.m4 b/m4/dune_common.m4
index a065f2e2c..9dcf65e6e 100644
--- a/m4/dune_common.m4
+++ b/m4/dune_common.m4
@@ -28,6 +28,7 @@ AC_DEFUN([DUNE_COMMON_CHECKS],
   AC_REQUIRE([DUNE_CHECKFINAL])
   AC_REQUIRE([DUNE_CHECKUNUSED])
   AC_REQUIRE([DUNE_CXX11_STD_DECLVAL_CHECK])
+  AC_REQUIRE([DUNE_CXX11_IS_INDEXABLE_SUPPORT_CHECK])
   AC_REQUIRE([DUNE_CHECK_CXA_DEMANGLE])
   AC_REQUIRE([DUNE_SET_MINIMAL_DEBUG_LEVEL])
   AC_REQUIRE([DUNE_PATH_XDR])
@@ -40,7 +41,7 @@ AC_DEFUN([DUNE_COMMON_CHECKS],
   ])
   DUNE_ADD_MODULE_DEPS([dune-common], [STDTHREAD], [${STDTHREAD_CPPFLAGS}],
                        [${STDTHREAD_LDFLAGS}], [${STDTHREAD_LIBS}])
-  
+
   dnl check for programs
   AC_REQUIRE([AC_PROG_CC])
   # add -Wall if the compiler is gcc
-- 
GitLab