2

I apologize for the title, but I wasn't able to think of anything better considering how peculiar this issue is.

I found this issue inside over a thousand-line long header, and eventually was able to narrow it down to the following code sample:

#include <vector>
#include <algorithm>
#include <type_traits>

namespace cpplinq
{
    template <typename T>
    struct IEnumerable : private std::vector<T>
    {
        template <typename ACont, typename U>
        friend IEnumerable<U> LINQ(ACont);      
    };

    template <typename ACont, typename T = typename std::decay<decltype(*(std::declval<typename std::remove_reference<ACont>::type>().begin()))>::type>
    IEnumerable<T> LINQ(ACont cont)
    {
        IEnumerable<T> ret;
        std::for_each(cont.begin(), cont.end(), [&ret](T val) {ret.push_back(val); });
        return ret;
    }
}

int main()
{
    auto mvalue = cpplinq::LINQ(std::vector<int>{1,2}); // compiles
    auto mmvalue = cpplinq::LINQ(std::vector<int>{1,2}); // doesn't compile 
    auto mmmvalue = cpplinq::LINQ(std::vector<int>{1,2}); // doesn't compile
    return 0;
}

Please ignore the function and struct names as they aren't relevant here.

When compiled with clang 3.4.1 or 4.0.0, the "mvalue" line compiles, and the two following lines don't, despite being syntactically identical.

https://godbolt.org/g/qaJzyQ - The version with only the "mvalue" line.
https://godbolt.org/g/T9DZek - The version with all three.

What's even more peculiar is that if you remove the "namespace cpplinq", and the associated braces, the entire thing compiles without a hitch, as shown here: https://godbolt.org/g/0Wl3jV

redspah
  • 257
  • 3
  • 9
  • Of-topic: http://stackoverflow.com/questions/4353203/thou-shalt-not-inherit-from-stdvector – Jonas May 04 '17 at 20:32
  • Offtopic. The good or bad design of the class isn't the point here, it's the inconsistency in compiling it with clang. – redspah May 04 '17 at 20:35
  • @redspah, I have a feeling that `*xxx.begin()` is tried to be evaluated, which is impossible. May be you could try to `declval` the iterator instead? – Incomputable May 04 '17 at 20:38
  • @Incomputable I'm unsure of what are you asking me to do. Do you mean replacing the `*xxx.begin()` with `declval` inside the templated part or inside the function body? If it's the former then I can't see how it could be "declvaled" even more, if the latter then unfortunately I don't know how to do it. – redspah May 04 '17 at 20:44
  • @redspah, I mean after stripping off reference, try to get `::iterator`, and declval that. Basically `decltype(*std::declval::type::iterator>())`. Trying that introduced enourmous error list. – Incomputable May 04 '17 at 20:47
  • Got it working. Replace `T` with this `typename T = typename std::remove_reference::type::iterator>())>::type`. – Incomputable May 04 '17 at 20:49
  • 1
    Clang appears to declare the `LINQ` friend function declaration without merging the default argument for `T` of the already existing declaration (the definition). Perhaps it is a mis-implementation of this rule: "If a friend function template declaration specifies a default template-argument, that declaration shall be a definition and shall be the only declaration of the function template in the translation unit.", which only applies if the friend function declaration gives the default argument. Note that if you call it with `::LINQ`, it also fails when you remove the namespace. – Johannes Schaub - litb May 04 '17 at 20:52
  • @Incomputable I don't think we're on the same page here. I just tried the edit you proposed and nothing changed: https://godbolt.org/g/DXs0jV Besides, I'd rather not rely on anything more than `ACont` having `begin()` and `end()` defined. – redspah May 04 '17 at 20:53
  • 1
    That would explain why the first call works, because at that time, the friend function declaration wasn't instantiated yet. – Johannes Schaub - litb May 04 '17 at 20:54
  • @redspah, after reloading the file it stopped compiling ... Usually a type that has begin and end will have `iterator` type alias. My point is that doing anything after the declval call will try to evaluate it, which is the case with `*(std::declval::type>().begin())`. – Incomputable May 04 '17 at 20:57
  • @JohannesSchaub-litb That does sound like it's the issue, thanks. Do you have any ideas on how to fix it? If all else fails I'll just change the inheritance of std::vector to be public instead of private, but I'd rather avoid that if possible. – redspah May 04 '17 at 20:57
  • @Incomputable Unfortunately, it only compiles on gcc. Try to compile it with clang 3.4.1 and flag "-std=c++11" instead. – redspah May 04 '17 at 21:00
  • 1
    @redspah you can make a LINQ_impl function and a non-friend wrapper LINQ that has the default argument, but the code is in LINQ_impl. – Johannes Schaub - litb May 04 '17 at 21:01
  • @JohannesSchaub-litb I'm very sorry, but I can't figure out how do to the thing you're just described. Would you mind quickly writing down the implementation of your idea? – redspah May 04 '17 at 21:06
  • 1
    @redspah http://coliru.stacked-crooked.com/a/7c4b0e88ab2e9e4f – Johannes Schaub - litb May 04 '17 at 21:18
  • @JohannesSchaub-litb Massive thanks for this. Now I get it, and shall use it in the full file. – redspah May 04 '17 at 21:20

0 Answers0