Search code examples
c++templatesgccsfinaeenable-if

Infinite template recursion because no bool expression optimisation only with gcc


I'm working on a lexical caster. See below the simplified, problematic part of the code below. The code compiles with clang and msvc, but fails to compile with gcc. It seems, that in the first argument of lexicalCast's std::enable_if gcc tries to evaluate all operand of the bool expression before evaluating it, while clang and msvc makes optimization and ignores the evaluation of LexicalCastable<>:: value if the std::is_same expressions failed. My questions would be:

  • do the standard specifies, which behaviour is correct?
  • how can I solve this problem to be able to compile this code with GCC?

Code:

#include <type_traits>
#include <string>

template<typename From, typename To>
class LexicalCastable;

template<typename To, typename From> typename
    std::enable_if<
           !std::is_same<std::string, To>::value
        && !std::is_same<std::string, From>::value
        && LexicalCastable<From, std::string>::value
        && LexicalCastable<std::string, To>::value,
To>::type lexicalCast(const From &from) {
    return lexicalCast<To>(lexicalCast<std::string>(from));
}

template<typename From, typename To>
class LexicalCastable
{
    template<typename TT>
    static auto test(int)
        -> decltype(lexicalCast<TT>(std::declval<From>()), std::true_type{});

    template<typename>
    static auto test(...)->std::false_type;

public:
    static const bool value = decltype(test<To>(0))::value;
};

static_assert(!LexicalCastable<std::string, std::string>::value, "");

Errors with gcc:

prog.cpp: In substitution of ‘template<class TT> static decltype ((lexicalCast<TT>(declval<From>()), std::true_type{})) LexicalCastable<From, To>::test(int) [with TT = std::__cxx11::basic_string<char>]’:
prog.cpp:35:45:   required from ‘const bool LexicalCastable<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >::value’
prog.cpp:19:3:   required by substitution of ‘template<class To, class From> typename std::enable_if<(((((! std::is_constructible<To, From>::value) && (! std::is_same<std::__cxx11::basic_string<char>, To>::value)) && (! std::is_same<std::__cxx11::basic_string<char>, From>::value)) && LexicalCastable<From, std::__cxx11::basic_string<char> >::value) && LexicalCastable<std::__cxx11::basic_string<char>, To>::value), To>::type lexicalCast(const From&) [with To = std::__cxx11::basic_string<char>; From = <missing>]’
prog.cpp:29:30:   required by substitution of ‘template<class TT> static decltype ((lexicalCast<TT>(declval<From>()), std::true_type{})) LexicalCastable<From, To>::test(int) [with TT = std::__cxx11::basic_string<char>]’
prog.cpp:35:45:   required from ‘const bool LexicalCastable<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >::value’
prog.cpp:19:3:   required by substitution of ‘template<class To, class From> typename std::enable_if<(((((! std::is_constructible<To, From>::value) && (! std::is_same<std::__cxx11::basic_string<char>, To>::value)) && (! std::is_same<std::__cxx11::basic_string<char>, From>::value)) && LexicalCastable<From, std::__cxx11::basic_string<char> >::value) && LexicalCastable<std::__cxx11::basic_string<char>, To>::value), To>::type lexicalCast(const From&) [with To = std::__cxx11::basic_string<char>; From = <missing>]’
prog.cpp:29:30:   [ skipping 889 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
prog.cpp:35:45:   required from ‘const bool LexicalCastable<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >::value’
prog.cpp:19:3:   required by substitution of ‘template<class To, class From> typename std::enable_if<(((((! std::is_constructible<To, From>::value) && (! std::is_same<std::__cxx11::basic_string<char>, To>::value)) && (! std::is_same<std::__cxx11::basic_string<char>, From>::value)) && LexicalCastable<From, std::__cxx11::basic_string<char> >::value) && LexicalCastable<std::__cxx11::basic_string<char>, To>::value), To>::type lexicalCast(const From&) [with To = std::__cxx11::basic_string<char>; From = <missing>]’
prog.cpp:29:30:   required by substitution of ‘template<class TT> static decltype ((lexicalCast<TT>(declval<From>()), std::true_type{})) LexicalCastable<From, To>::test(int) [with TT = std::__cxx11::basic_string<char>]’
prog.cpp:35:45:   required from ‘const bool LexicalCastable<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >::value’
prog.cpp:19:3:   required by substitution of ‘template<class To, class From> typename std::enable_if<(((((! std::is_constructible<To, From>::value) && (! std::is_same<std::__cxx11::basic_string<char>, To>::value)) && (! std::is_same<std::__cxx11::basic_string<char>, From>::value)) && LexicalCastable<From, std::__cxx11::basic_string<char> >::value) && LexicalCastable<std::__cxx11::basic_string<char>, To>::value), To>::type lexicalCast(const From&) [with To = std::__cxx11::basic_string<char>; From = <missing>]’
prog.cpp:40:43:   required from here
prog.cpp:29:49: fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
   -> decltype(lexicalCast<TT>(std::declval<From>()), std::true_type{});
                               ~~~~~~~~~~~~~~~~~~^~

Solution

  • how can I solve this problem to be able to compile this code with GCC

    You might use std::conjunction (C++17) which guaranty short-circuit

    template <typename To, typename From>
    typename std::enable_if<
        std::conjunction<
               std::negation<std::is_same<std::string, To>>,
               std::negation<std::is_same<std::string, From>>,
               LexicalCastable<From, std::string>
               LexicalCastable<std::string, To>>::value,
        To>::type
    lexicalCast(const From &from)
    {
        return lexicalCast<To>(lexicalCast<std::string>(from));
    }