[type_traits] is_complete<T> possible realization

Hi, Yesterday I had a need to detect, is class forward declared or is it complete. Here are some thoughts: template <class T, int Misc> struct is_complete { typedef char false_type; typedef int true_type; template <class T1> static typename boost::enable_if_c<sizeof(T1), true_type>::type test(int); template <class T1> static false_type test(...); enum ENU { value = ( sizeof(test<T>(0)) == sizeof(true_type) )}; }; 'Misc' parameter is required to work around compilers one time instantiation: struct incomplete; template <int N> struct incomplete_template; struct complete{}; struct complete_abstract{ virtual void foo() =0; }; void test(); void foo1() { cout << is_complete<incomplete, __LINE__>::value << is_complete<incomplete_template<0> , __LINE__>::value << is_complete<incomplete_template<1> , __LINE__>::value << is_complete<complete, __LINE__>::value << is_complete<complete_abstract, __LINE__>::value << endl; } struct incomplete{}; template <> struct incomplete_template<0>{}; void foo2() { cout << is_complete<incomplete, __LINE__>::value // Without second template parameter it would be still returning 0 << is_complete<incomplete_template<0> , __LINE__>::value << is_complete<incomplete_template<1> , __LINE__>::value << is_complete<complete, __LINE__>::value << is_complete<complete_abstract, __LINE__>::value << endl; } int main() { foo1(); // Outputs 00011 foo2(); // Outputs 11011 return 0; } I was thinking of changing 'Misc' parameter definition to something like this: template <class T, class... Misc> struct is_complete ; Than it can be used in template functions/classes just like this: template <class T, class... SomeClasses> void test_function() { is_complete<T, SomeClasses..., boost::mpl::int_<__LINE__> >::value; } So, Is there interest in such metafunction? Can somebody give advices how to improve it? (I`m not sure that implementation is portable, I`ve tested it only on GCC-4.6 and CLANG-3.0, MSVC compiler will be available only tomorrow) -- Best regards, Antony Polukhin

So,
Is there interest in such metafunction? Can somebody give advices how to improve it? (I`m not sure that implementation is portable, I`ve tested it only on GCC-4.6 and CLANG-3.0, MSVC compiler will be available only tomorrow)
I don't have an answer to your question, except this has been suggested before - and got shot down because the definition of is_complete<T> would need to change depending where it was instantiated - and you can't do that (one definition rule). BTW if all you want is an assertion that a type is complete then: BOOST_STATIC_ASSERT(sizeof(T)); would do the trick. John.

2012/11/5 John Maddock <boost.regex@virgin.net>:
I don't have an answer to your question, except this has been suggested before - and got shot down because the definition of is_complete<T> would need to change depending where it was instantiated - and you can't do that (one definition rule).
Searching for is_complete in Internet gave me a non-compiling function that even in theory can not work with abstract classes. In my proposal ODR is bypassed by the set of 'Misc' parameters: template <class T, class T1, class T2, class T3> void test_function() { // Parameters after T are used to cheat on ODR is_complete<T, T1, T2, T3, boost::mpl::int_<__LINE__> >::value; }
BTW if all you want is an assertion that a type is complete then:
BOOST_STATIC_ASSERT(sizeof(T));
would do the trick.
That is exactly what I`m trying not to do: template <bool IsAwailable> struct stl_specific_work{ static void act() { throw std::logic_error("Attempt to use ctype<char16_t> for non 'C' locale: your STL implementation does not specialize ctype<char16_t>"); } }; template <> struct stl_specific_work<true>{ static void act() { // Some work with ctype<char16_t> } }; if (std::locale() != std::locale::classic()) { stl_specific_work< !is_complete<ctype<char16_t> /*, SomeMoreTemplateParameters*/ >::value >::act(); } I need to work with ctype<char16_t> if it is available. Without is_complete<> I would be required to forbid ctype<char16_t> usage even on platforms that do support it and even if user provided it`s own implementation. -- Best regards, Antony Polukhin

2012/11/5 Antony Polukhin <antoshkka@gmail.com>
I don't have an answer to your question, except this has been suggested before - and got shot down because the definition of is_complete<T> would need to change depending where it was instantiated - and you can't do
2012/11/5 John Maddock <boost.regex@virgin.net>: that
(one definition rule).
Searching for is_complete in Internet gave me a non-compiling function that even in theory can not work with abstract classes.
In my proposal ODR is bypassed by the set of 'Misc' parameters:
template <class T, class T1, class T2, class T3> void test_function() { // Parameters after T are used to cheat on ODR is_complete<T, T1, T2, T3, boost::mpl::int_<__LINE__> >::value; }
BTW if all you want is an assertion that a type is complete then:
BOOST_STATIC_ASSERT(sizeof(T));
would do the trick.
That is exactly what I`m trying not to do:
template <bool IsAwailable> struct stl_specific_work{ static void act() { throw std::logic_error("Attempt to use ctype<char16_t> for non 'C' locale: your STL implementation does not specialize ctype<char16_t>"); } };
template <> struct stl_specific_work<true>{ static void act() { // Some work with ctype<char16_t> } };
if (std::locale() != std::locale::classic()) { stl_specific_work< !is_complete<ctype<char16_t> /*, SomeMoreTemplateParameters*/
::value >::act(); }
I need to work with ctype<char16_t> if it is available. Without is_complete<> I would be required to forbid ctype<char16_t> usage even on platforms that do support it and even if user provided it`s own implementation.
IMO defer what should fail in compile time to runtime is not a good idea generally. It' the programmar who should know "your STL implementation does not specialize ctype<char16_t>" not the end user. And BOOST_STATIC_ASSERT won't prevent you from providing ctype<char16_t> support as it should compile on the platforms that do support it.

On 05/11/2012 09:34, Antony Polukhin wrote:
struct incomplete; template <int N> struct incomplete_template; struct complete{}; struct complete_abstract{ virtual void foo() =0; };
void test();
void foo1() { cout << is_complete<incomplete, __LINE__>::value << is_complete<incomplete_template<0> , __LINE__>::value << is_complete<incomplete_template<1> , __LINE__>::value << is_complete<complete, __LINE__>::value << is_complete<complete_abstract, __LINE__>::value << endl; }
What if I do in another TU is_complete<incomplete, __LINE__> at the same line number and when incomplete is actually complete?

2012/11/9 Mathias Gaunard <mathias.gaunard@ens-lyon.org>:
On 05/11/2012 09:34, Antony Polukhin wrote:
void foo1() { cout << is_complete<incomplete, __LINE__>::value << is_complete<incomplete_template<0> , __LINE__>::value << is_complete<incomplete_template<1> , __LINE__>::value << is_complete<complete, __LINE__>::value << is_complete<complete_abstract, __LINE__>::value << endl; }
What if I do in another TU is_complete<incomplete, __LINE__> at the same line number and when incomplete is actually complete?
You can always add some more misc template parameters: void foo1() { struct unique_foo1_tag; cout << is_complete<incomplete, __LINE__, unique_foo1_tag>::value << is_complete<incomplete_template<0> , __LINE__, unique_foo1_tag>::value << is_complete<incomplete_template<1> , __LINE__, unique_foo1_tag>::value << is_complete<complete, __LINE__, unique_foo1_tag>::value << is_complete<complete_abstract, __LINE__, unique_foo1_tag>::value << endl; } And you can also use __COUNTER__ on MSVC. -- Best regards, Antony Polukhin

What if I do in another TU is_complete<incomplete, __LINE__> at the same line number and when incomplete is actually complete?
You can always add some more misc template parameters:
void foo1() { struct unique_foo1_tag; cout << is_complete<incomplete, __LINE__, unique_foo1_tag>::value << is_complete<incomplete_template<0> , __LINE__, unique_foo1_tag>::value << is_complete<incomplete_template<1> , __LINE__, unique_foo1_tag>::value << is_complete<complete, __LINE__, unique_foo1_tag>::value << is_complete<complete_abstract, __LINE__, unique_foo1_tag>::value << endl; }
And you can also use __COUNTER__ on MSVC.
__COUNTER__ won't do what you want across multiple TU's. The problem runs deeper: Translation Units a.cpp and b.cpp each instantiate a function foo<T> which uses is_complete<U>, but U is complete in TU a.cpp, and incomplete in b.cpp (because they include different headers). You now have an insidious ODR violation which would be very hard to diagnose and track down - which version of foo<T> is the correct one? John.

On Fri, Nov 9, 2012 at 9:38 AM, Antony Polukhin <antoshkka@gmail.com> wrote:
You can always add some more misc template parameters:
No, you can't. You always have the same exact problem if you just add more template parameters, only you're hiding it more. That said, I'm not as convinced that this type of metafunction is impossible, it's just that your current approach cannot be made to succeed. I believe you should be able to create the metafunction without directly violating ODR if you encode the checking process into the evaluation of a default template argument, or if you use template aliases. This works because the evaluation of the condition happens at the time the parameters are passed (not inside of the template), meaning that you don't have to worry about memoization, and since the result is a template argument, you don't violate ODR either (at least not directly). Testing this approach yields promising results in clang and gcc: http://codepaste.net/xfgcu8 The linked code works fine in Clang 3.1 and GCC 4.7, with C++11 support on. I believe that you can even hide the default parameter through the use of a template alias and still technically avoid memoization, but while Clang seems to agree with me on that, GCC does not. There is an caveat, though. While I believe this gets around ODR directly, you still have to be careful when using it, as would be the case for any kind of completeness checker. Consider the type of ODR problem in the following: // Begin Translation Unit A struct foo; template< class T > struct your_code { // Do something valid when the type is incomplete }; // End Tranlation Unit A // Begin Translation Unit B struct foo {}; template< class T > struct your_code { // Do something valid when the type is complete }; // End Tranlation Unit B ////////// While the above code might seem contrived, it could definitely happen when dealing with forward declarations of classes. Because of this, I'd recommend in general only using a completeness checker with something like a static_assert anyway, forcing compilation to fail when you don't get the result that you'd expect, otherwise you have to be extremely careful. -- -Matt Calabrese

On Fri, Nov 9, 2012 at 12:18 PM, Matt Calabrese <rivorus@gmail.com> wrote:
While the above code might seem contrived, it could definitely happen when dealing with forward declarations of classes. Because of this, I'd recommend in general only using a completeness checker with something like a static_assert anyway, forcing compilation to fail when you don't get the result that you'd expect, otherwise you have to be extremely careful.
In case it wasn't clear, I was implying in the code sample that the template was instantiated with foo in both translation units (forgot to show that). Since in one translation unit foo is incomplete and in the other it is complete, you could potentially violate ODR by producing a different definition for you_code< foo > in each translation unit. Of course, you could get around this by using the ODR trick from the completeness checker (putting the call to the metafunction in a default argument to the template), but then you're effectively pushing out the problem to a higher-level. In order to be completely safe, you'd have to propagate the checking all the way out to the top level instantiation. -- -Matt Calabrese
participants (6)
-
Antony Polukhin
-
John Maddock
-
Mathias Gaunard
-
Matt Calabrese
-
Peter Dimov
-
TONGARI