2

I get a compiler error in clang++. MSVC++ is happy. I believe my declarations are correct.

Am I incorrect in my beliefs and I am "lucky" in MSVC? Is there a non #ifndef _MSC_VER ... public: way to make this work in both compilers?

I'd like to keep the constructor private. The real code is slightly more complex. (additional template meta-programming and "perfect forwarding") The following is a distilled version for the question and to isolate the issue as much as possible. I've tried a number of variations for the friend declaration. The one that "seems best" is shown in the example.

#include<type_traits>

template<typename T> class Wrap;

template<typename T, 
    typename std::enable_if<std::is_class<T>::value, T>::type* = nullptr >
Wrap<T> make_wrapper( T i )
{
    return Wrap<T>( i );
}

template<typename T>
class Wrap : T
{
    friend Wrap<T> make_wrapper<T,nullptr>( T );
private:
    Wrap( T v ) : T( v ) {}
};

template<typename T>
class Imp
{
    T x;
public:
    Imp( T v ) {}
};

int main()
{
    auto wrap = make_wrapper( Imp<int>( 1 ) );
    return 0;
}

clang++:

$ clang++ --version
Debian clang version 3.5-1~exp1 (trunk) (based on LLVM 3.5)
Target: x86_64-pc-linux-gnu
Thread model: posix

$ clang++ -std=c++11 test.cpp
test.cpp:8 : 12 : error : calling a private constructor of class 'Wrap<Imp<int> >'
return Wrap<T>( i );
^
test.cpp:30 : 17 : note : in instantiation of function template specialization 'make_wrapper<Imp<int>, nullptr>' requested here
auto wrap = make_wrapper( Imp<int>( 1 ) );
^
test.cpp:16 : 5 : note : declared private here
Wrap( T v ) : T( v ) { }
^
1 error generated.

cl

Microsoft Visual Studio Professional 2013
Version 12.0.30501.00 Update 2

1>------ Build started: Project: Test1, Configuration: Debug Win32 ------
1>  Test1.cpp
1>  Test1.vcxproj -> C:\cygwin64\home\username\Test1\Debug\Test1.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
ErnieE
  • 143
  • 1
  • 6
  • FWIW, g++ 4.8.2 thinks your code is good. – R Sahu Jul 01 '14 at 04:00
  • Thanks I ~should~ be checking against g++ as well for the code in question. I suspect it will save me time in the future if I don't let too many additional quirks permeate the code. I've been avoiding spending too much time on this so far. I guess it is now time to add g++ into the list. – ErnieE Jul 01 '14 at 15:13

1 Answers1

1

Template friends are notoriously complicated. I don't know for sure whether Clang is right, but it could be that your SFINAE trick inside the function template arguments runs afoul of

14.5.4 Friends [temp.friend]

9 When a friend declaration refers to a specialization of a function template, the function parameter declarations shall not include default arguments, nor shall the inline specifier be used in such a declaration.

C++11 introduced default template arguments for function templates, and it could be that clang interprets the above differently as g++/MSVC. It is fixable by doing SFINAE on the return type instead:

#include<type_traits>

template<typename T> class Wrap;

template<typename T>
using WrapRet = typename std::enable_if<std::is_class<T>::value, Wrap<T>>::type;

template<typename T>
WrapRet<T> make_wrapper( T i )
{
    return Wrap<T>( i );
}

template<typename T>
class Wrap : T
{
    friend WrapRet<T> make_wrapper<T>( T );
private:
    Wrap( T v ) : T( v ) {}
};

template<typename T>
class Imp
{
    T x;
public:
    Imp( T v ) {}
};

int main()
{
    auto wrap = make_wrapper( Imp<int>( 1 ) );
    return 0;
}

Live Example that works with both clang and g++.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Of course it ~would~ work for the simple isolated case! Thanks for the idea! In the "real world" of the non-isolated code I avoided moving the declaration into the return type or as a parameter because the resulting code becomes MUCH more challenging to read. When I tried (just now) moving the enable_if to the return type, I got other interesting errors. (I'll look into those later.) I used to love templates, but wow the C++11 template meta syntax can be a bit much. – ErnieE Jul 01 '14 at 15:08
  • @ErnieE with a few helper types like `WrapRet` readability should remain OK-ish. Looking forward to reduced test case if your real still won't work out – TemplateRex Jul 01 '14 at 16:26
  • using 'using' is about the only thing that has made what is otherwise a mess (IMO: remember I REALLY like templates). The private constructor works now, but I don't have time to track down what is tripping up one other member I'd like to keep private. I was NOT aware that the enable_if construct would function in a helper. I will DEFINITELY be moving some other declarations around (where appropriate) in the near future. **If** you are interested the project is on [link]https://github.com/ErnieE5/ee5_util/blob/master/inc/marshaling.h Thanks for your help. – ErnieE Jul 01 '14 at 20:27
  • @ErnieE yes, template aliases are SFINAE-friendly, they don't introduce a new type, simply an alias, and the `std::enable_if` is really done as if it were written directly in the return type. In C++14, all the current traits from `` will get `sometrait_t` versions, where the `_t` suffix means: `typename sometrait::type`. There is even a proposal to add the `_v` suffix that does the `::value` extraction. So you can then (2017 and beyond) also write directly `std::enable_if_t, wrap>` :-) – TemplateRex Jul 01 '14 at 20:32
  • I also figured out the issue with other member that I wanted private. Switching compilers produced the 'duh' moment. In **general** clang++ gives MUCH better diagnostics, but in this case MSVC pointed me directly to the issue and clang++ was lacking. I've been looking forward to the _t aliases. The _v const aliases would ALSO be exceptionally helpful. From the very start ('94!), I've almost always used typedefs for most of my templates. Some have complained that typedefs are obfuscation. I prefer the simplicity of expression. `using` is second only argument packs as my most favorite feature. – ErnieE Jul 01 '14 at 23:25